Using T4 template to force PascalCase (TitleCase) for entities in Entity Framework 6

c# entity-framework entity-framework-6 t4 visual-studio

Question

It turned out that changing the default T4 template was really rather simple. InsideGetTypeName Exists ais StructuralType Verify that it can handle every non-primitive type. The bulk of my problems were mostly resolved by that. Then, all that remained was forCtrl-F for important phrases. When I first asked this question, I was simply so frustrated that I couldn't think straight.

--

A database has been sent to me with all column and table names in snake case. There is no way to alter the database. To automatically produce all classes, members, properties, navigation properties, etc. in PascalCase, I want to harness the power of T4 templates (TitleCase).

I am now quite close, but I am beginning to get stuck.

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; }
    }
}

The class name, navigation property names, certain return values for the navigation property, and file name are all obtained as shown above. The function Object() { [native code] } name, function Object() { [native code] } assignments, properties, and the remaining return values for the navigation properties are where I am having trouble.

Right now, I'm simply manually changing every string reference usingCultureInfo.CurrentCulture.TextInfo.ToTitleCase(string).Replace("_", "") .

For the class name, as an illustration:

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)));
    }

Adapted To:

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)));
    }

Although it has been a drawn-out and tiresome procedure, it is still better than nothing at all. I was hoping that you guys would be able to provide me some further guidance or a little better option. I've never used a customised T4 template before. In some of the situations, I feel like I should be able to finish things in one shot.TypeManager methods. Just where, I'm not completely sure.

T4 templates were mentioned in a few blog articles and StackOverflow queries about doing this, but sadly the templates were never provided.

I would value any advice, even if it isn't always the best course of action.


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();
        }
    }
}

The test driver I've created as of late to evaluate my DBContext is shown above. The first line fails with an error notice that reads as follows: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; }
    }
}

That is the Pokemon.cs file I currently have, which T4 produced. And under PokeDBContext.cs, here is my DbSet declaration:

public virtual DbSet<Pokemon> Pokemon { get; set; }

As a result of the classes being correctly configured, I am receiving the right Intellisense. Just a second, pleaseInvalidOperationException I attempt to access the entities every time. Keep in mind that the actual Database object ispokemon.

1
1
1/11/2017 12:02:33 AM

Popular Answer

You're on the right track, but what you really need is a straightforward translation from snake case to PascalCase. My worry is that with these modifications alone, EF will lose column mappings (assuming you're utilising model/database first).

To do what you're asking, move your excellent CultureInfo. CurrentCulture. TextInfo. ToTitleCase(name). To a reusable method in the CodeStringGenerator class, replace("_", "");

public string PascalCase(string name)
{
    return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name).Replace("_", "");
}

The T4 template for the function Object() { [native code] } should then be changed to:

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)#>();
<#
    }
#>
}

After completing all of these steps, your code will compile; nevertheless, queries are likely to fail since the edmx still uses snake case naming and lacks mappings to your real database. As a result, the edmx file also has to be edited. There are a few instances here: How can I use Oracle's Entity Framework support to force Pascal case?, but at this point, you begin to doubt if the effort is worthwhile.

EDIT replacing the EDMX with (better option)

First, undo your T4 adjustments; you no longer need them. then apply the link example. I gently altered it to capitalise your title. This will be used by me for a related task:

// 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;
}
0
5/23/2017 12:33:51 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow