【问题标题】:Using T4 template to force PascalCase (TitleCase) for entities in Entity Framework 6使用 T4 模板为 Entity Framework 6 中的实体强制使用 PascalCase (TitleCase)
【发布时间】:2017-01-09 05:20:06
【问题描述】:

事实证明,简单地修改默认的 T4 模板实际上非常容易。在GetTypeName 内部有一个is StructuralType 检查,它处理所有非原始类型。这很好地解决了我的大部分问题。那么这只是一个Ctrl-F 关键字的问题。当我最初发布这个问题时,我的挫败感只是让我变得最好。

--

我得到了一个数据库,其中所有的表名和列名都以snake_case 命名。无法更改数据库。我希望利用 T4 模板的强大功能,在 PascalCase (TitleCase) 中自动生成所有类、成员、属性、导航属性等。

到目前为止,我已经很接近了,但我开始陷入困境。

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

如上所示,我得到了类名、导航属性名、一些导航属性返回值和文件名。我卡住的地方是构造函数名称、构造函数分配、属性和其余导航属性返回值。

现在,我只是手动将字符串的所有引用替换为 CultureInfo.CurrentCulture.TextInfo.ToTitleCase(string).Replace("_", "")

例如,对于类名:

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

改为:

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

这是一个漫长而乏味的过程,但总比没有好。我希望你们能给我一些进一步的建议,或者更清洁的解决方案?这是我第一次使用任何类型的自定义 T4 模板。感觉就像我应该能够在一些TypeManager 方法中一举搞定其中的一些。我只是不完全确定在哪里。

我发现了几个 StackOverflow 问题和一些关于使用 T4 模板执行此操作的博客文章,但不幸的是,这些模板从未共享过。

即使它不一定是一个完美的解决方案,我也希望得到任何指导。


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

以上是我为测试我的 DBContext 而编写的当前测试驱动程序。第一行失败并显示以下消息: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; }
    }
}

这是我当前通过 T4 生成的 Pokemon.cs 文件。这是我在 PokeDBContext.cs 中的 DbSet 声明:

public virtual DbSet&lt;Pokemon&gt; Pokemon { get; set; }

由于课程设置正确,我得到了正确的 Intellisense。每当我尝试访问实体时,我都会立即收到InvalidOperationException。请注意,真正的数据库实体是pokemon.

【问题讨论】:

  • 你为什么不考虑在这个链接上使用 EF POCO marketplace.visualstudio.com/…
  • @Aravind 这个工具看起来真的很棒,而且似乎它应该完全满足我的需要。但是,在设置连接字符串时,我一直遇到问题。 EF 向导创建的连接字符串不起作用。我不断收到有关 System.Data.EntityClient 提供程序无法加载的错误消息。但它适用于正常的转换。
  • 尼克。请使用错误的屏幕截图以及发生错误的相应代码更新您的帖子
  • @Aravind 我用我当前的错误更新了这个问题。

标签: c# entity-framework visual-studio entity-framework-6 t4


【解决方案1】:

您在正确的轨道上,但您想要的是一个简单的蛇形案例到 PascalCase 的转换。我担心 EF 会因这些更改而丢失列映射(如果您首先使用模型/数据库)。

按照您的要求,移动您出色的 CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name).Replace("_", "");到 CodeStringGenerator 类中的可重用方法

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

然后将T4模板中的构造函数改为:

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

完成所有这些后,您的代码将编译,但查询可能会失败,因为 edmx 仍然具有蛇案例命名,因此没有映射到您的实际数据库。因此,您还需要编辑 edmx 文件。这里有一些示例:How to force pascal case with Oracle's Entity Framework support?,但此时您开始怀疑是否值得付出努力。

编辑改为修改 EDMX(更好的选择)

首先还原您的 T4 更改,您不再需要它们。然后使用link 中的示例。我已经稍微修改了它以使用您的标题大小写。我将使用它来完成类似的任务:

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

【讨论】:

  • 我相信我遇到了这个确切的问题。我的一切看起来都很完美。从我的班级名称到我的 DbSet。这一切看起来正是我想要的。但后来我收到一个关于我的实体在当前上下文中不存在的错误。我突然意识到 LINQ to SQL 试图在带有 entity_x 的数据库中查找 EntityX 会失败。我尝试为每个生成的类添加一个 Table 注释,但这似乎并没有解决我的问题。任何关于让 LINQ to SQL 正确映射的方法?我真的不想在 C# 中使用蛇案例实体。
  • 您已编辑 edmx 文件,该文件包含在 EntitySetMapping 元素中映射到实体的名称。答案中的问题是解决此问题的少数示例之一 (stackoverflow.com/questions/6505088/…),但它们都涉及代码优先或编辑 edmx。
  • 非常感谢您帮助我。我已经用我的InvalidOperationException. 更新了我的OP,我在.edmx 中搜索了我可能需要更改EntitySetMapping 的地方,但这一切看起来都像我预期的那样。还有其他的cmets吗?
  • 好的,你的问题整理构造函数在答案的第二个代码块中得到解决。现在,如果您要修改 EDMX,那么您甚至不需要编辑 T4 模板!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-13
  • 2014-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多