Risulta, la semplice modifica del modello T4 predefinito era in realtà davvero semplice. All'interno di GetTypeName
esiste un controllo is StructuralType
che gestisce tutti i tipi non primitivi. Ciò ha risolto abbastanza bene la maggior parte dei miei problemi. Quindi era solo questione di Ctrl-F
per le parole chiave. La mia frustrazione stava appena ottenendo il meglio di me quando originariamente ho postato questa domanda.
-
Mi è stato fornito un database con tutti i nomi delle tabelle e i nomi delle colonne in snake_case. Il database non può essere modificato. Spero di utilizzare la potenza dei modelli T4 per generare automaticamente tutte le classi, i membri, le proprietà, le proprietà di navigazione e così via in PascalCase (TitleCase).
Finora, sto diventando decentemente vicino, ma sto iniziando a rimanere bloccato.
namespace PokeDB
{
using System;
using System.Collections.Generic;
public partial class Ability
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public ability()
{
this.ability_changelog = new HashSet<ability_changelog>();
this.ability_flavor_text = new HashSet<ability_flavor_text>();
this.ability_names = new HashSet<ability_names>();
this.ability_prose = new HashSet<ability_prose>();
this.conquest_pokemon_abilities = new HashSet<conquest_pokemon_abilities>();
this.pokemon_abilities = new HashSet<pokemon_abilities>();
}
public long id { get; set; }
public string identifier { get; set; }
public long generation_id { get; set; }
public bool is_main_series { get; set; }
public virtual generation Generation { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AbilityChangelog> AbilityChangelog { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AbilityFlavorText> AbilityFlavorText { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AbilityNames> AbilityNames { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AbilityProse> AbilityProse { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<ConquestPokemonAbilities> ConquestPokemonAbilities { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PokemonAbilities> PokemonAbilities { get; set; }
}
}
Come mostrato sopra, ho ottenuto il nome della classe, i nomi delle proprietà di navigazione, alcuni dei valori di ritorno delle proprietà di navigazione e il nome del file. Dove mi trovo bloccato sono il nome del costruttore, le assegnazioni del costruttore, le proprietà e il resto dei valori di ritorno delle proprietà di navigazione.
Al momento, sto semplicemente sostituendo manualmente tutti i riferimenti della stringa con CultureInfo.CurrentCulture.TextInfo.ToTitleCase(string).Replace("_", "")
.
Ad esempio, per il nome della classe:
public string EntityClassOpening(EntityType entity)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1}partial class {2}{3}",
Accessibility.ForType(entity),
_code.SpaceAfter(_code.AbstractOption(entity)),
_code.Escape(entity),
_code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));
}
Cambiato in:
public string EntityClassOpening(EntityType entity)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1}partial class {2}{3}",
Accessibility.ForType(entity),
_code.SpaceAfter(_code.AbstractOption(entity)),
CultureInfo.CurrentCulture.TextInfo.ToTitleCase(_code.Escape(entity)).Replace("_", ""),
_code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));
}
È stato un processo lungo e noioso, ma è meglio di assolutamente nulla. Speravo che voi ragazzi poteste avere qualche ulteriore consiglio per me o una soluzione più pulita? Questa è la mia prima volta che utilizzo qualsiasi tipo di modello T4 personalizzato. Sembra che dovrei essere in grado di batterne alcuni con un colpo solo in alcuni dei metodi di TypeManager
. Non sono del tutto sicuro di dove.
Ho trovato un paio di domande StackOverflow e un paio di post sul blog sull'utilizzo di modelli T4 per farlo, ma sfortunatamente i modelli non sono mai stati condivisi.
Anche se non è necessariamente una soluzione perfetta, apprezzerei qualsiasi quantità di guida.
using System.Linq;
namespace PokeDB
{
public class ConsoleDriver
{
public static readonly PokeDBContainer PokeDB = new PokeDBContainer();
public static void Main(string[] args)
{
System.Diagnostics.Debug.WriteLine(PokeDB.Pokemon);
var x = PokeDB.Pokemon.ToList();
}
}
}
Sopra è l'attuale driver di test che ho scritto per testare il mio DBContext. La prima riga non riesce con un messaggio Il seguente messaggio: An unhandled exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll Additional information: The entity type Pokemon is not part of the model for the current context.
namespace PokeDB
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
[Table("pokemon")]
public partial class Pokemon
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Pokemon()
{
this.Encounters = new HashSet<Encounter>();
this.PokemonAbilities = new HashSet<PokemonAbilities>();
this.PokemonForms = new HashSet<PokemonForms>();
this.PokemonGameIndices = new HashSet<PokemonGameIndices>();
this.PokemonItems = new HashSet<PokemonItems>();
this.PokemonMoves = new HashSet<PokemonMoves>();
this.PokemonStats = new HashSet<PokemonStats>();
this.PokemonTypes = new HashSet<PokemonTypes>();
}
public long Id { get; set; }
public string Identifier { get; set; }
public Nullable<long> SpeciesId { get; set; }
public long Height { get; set; }
public long Weight { get; set; }
public long BaseExperience { get; set; }
public long Order { get; set; }
public bool IsDefault { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Encounter> Encounters { get; set; }
public virtual PokemonSpecies PokemonSpecies { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PokemonAbilities> PokemonAbilities { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PokemonForms> PokemonForms { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PokemonGameIndices> PokemonGameIndices { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PokemonItems> PokemonItems { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PokemonMoves> PokemonMoves { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PokemonStats> PokemonStats { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PokemonTypes> PokemonTypes { get; set; }
}
}
Questo è il mio attuale file Pokemon.cs che è stato generato tramite T4. E questa è la mia dichiarazione DbSet all'interno di PokeDBContext.cs:
public virtual DbSet<Pokemon> Pokemon { get; set; }
Sto ottenendo l'Intellisense corretto poiché le classi sono configurate correttamente. Sto solo ricevendo un istante InvalidOperationException
ogni volta che provo ad accedere alle entità. Si noti che la vera entità Database è pokemon.
Sei sulla strada giusta, ma quello che vuoi è un semplice caso di serpente alla conversione di PascalCase. La mia preoccupazione è che EF perderebbe i mapping delle colonne (se si sta utilizzando il modello / database prima) con queste modifiche da solo.
Per fare ciò che stai chiedendo, sposta il tuo eccellente CultureInfo.CurrentCulture.TextInfo.ToTitleCase (name) .Replace ("_", ""); a un metodo riutilizzabile nella classe CodeStringGenerator
public string PascalCase(string name)
{
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name).Replace("_", "");
}
Quindi nel modello T4 per la modifica costruttore in:
public <#=code.Escape(codeStringGenerator.PascalCase(entity))#>()
{
<#
foreach (var edmProperty in propertiesWithDefaultValues)
{
#>
this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>;
<#
}
foreach (var navigationProperty in collectionNavigationProperties)
{
// for readability hold the type name in a variable
var typeName = typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
#>
// pascal case here
this.<#=code.Escape(codeStringGenerator.PascalCase(navigationProperty))#> = new HashSet<<#=code.Escape(codeStringGenerator.PascalCase(typeName))#>>();
<#
}
foreach (var complexProperty in complexProperties)
{
#>
this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>();
<#
}
#>
}
Fatto questo, il tuo codice verrà compilato, ma le query probabilmente falliranno a causa del fatto che edmx ha ancora la denominazione del caso snake, quindi nessuna mappatura al tuo vero database. Pertanto, dovrai anche modificare il file edmx. Ci sono alcuni esempi qui: Come forzare il caso pascal con il supporto di Entity Framework di Oracle? , ma a questo punto si inizia a chiedersi se ne vale la pena.
MODIFICA Modifica di EDMX (opzione migliore)
Per prima cosa ripristina le modifiche T4, non ne hai più bisogno. Quindi utilizzare l'esempio nel collegamento . L'ho modificato leggermente per usare la custodia del titolo. Userò questo per un compito simile:
// In the main method get your edmx/designer
//EDMX File location
string pathFile = @"c:\Path\To\DbModel.edmx";
//Designer location for EF 5.0
string designFile = @"c:\Path\To\DbModel.edmx.diagram";
// ...
// replace the PascalCase method with this
public static string PascalCase(string name, bool sanitizeName = true, bool pluralize = false)
{
// if pascal case exists
// exit function
Regex rgx = new Regex(@"^[A-Z][a-z]+(?:[A-Z][a-z]+)*$");
string pascalTest = name;
if (name.Contains("."))
{
string[] test = new string[] { };
test = name.Split('.');
if (rgx.IsMatch(test[1].ToString()))
{
return name;
}
}
else
{
if (rgx.IsMatch(name))
{
return name;
}
}
//Check for dot notations in namespace
string result;
bool contains = false;
string[] temp = new string[] { };
var namespc = string.Empty;
if (name.Contains("."))
{
contains = true;
temp = name.Split('.');
namespc = temp[0];
}
if (contains)
{
name = temp[1];
}
name = name.ToLowerInvariant(); // this may or may not be required
// Here's the simplified snake to pascal case
result = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name).Replace("_", "");
if (contains)
{
result = namespc.ToString() + "." + result;
}
if (pluralize)
{
result = Pluralize(result);
}
return result;
}