实体框架核心自定义脚手架

15

我已经完成了SQLServer数据库的脚手架建设,并在指定文件夹中创建了POCO对象。我希望它能够从我的基类继承。我还使用了存储库模式,因此我需要每个实体上都有Id键,并且我不希望在每次重新生成数据库时更改它。

脚手架模型示例

public partial class Food
{
     public int Id { get; set; }
     public string Name { get; set; }
     public string Description { get; set; }
     public double Price { get; set; }
}

期望结果:

public partial class Food : EntityBase
{
     public string Name { get; set; }
     public string Description { get; set; }
     public double Price { get; set; }
}

public class EntityBase : IEntityBase
{
    public int Id { get; set; }
}
5个回答

7

@Tornike Choladze的出色答案引导我走向了正确的方向,但在最新的.Net Core版本(2.0及更高版本),似乎需要以不同的方式来进行设置。

自定义实体类型生成器:

class MyEntityTypeGenerator : CSharpEntityTypeGenerator
{
    public MyEntityTypeGenerator(ICSharpUtilities cSharpUtilities) : base(cSharpUtilities) { }

    public override string WriteCode(IEntityType entityType, string @namespace, bool useDataAnnotations)
    {
        string code = base.WriteCode(entityType, @namespace, useDataAnnotations);

        var oldString = "public partial class " + entityType.Name;
        var newString = "public partial class " + entityType.Name + " : EntityBase";

        return code.Replace(oldString, newString);
    }
}

安装过程是由同一程序集中实现 IDesignTimeServices 接口的类完成的:

public class MyDesignTimeServices : IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
    {
        serviceCollection.AddSingleton<ICSharpEntityTypeGenerator, MyEntityTypeGenerator>();            
    }
}

1
这对于EF Core 2.2.4有效吗?看起来CSharpEntityTypeGenerator现在需要ICSharpHelper。此外,脚手架框架如何知道执行MyDesignTimeServices.ConfigureDesignTimeServices - LP13
1
是的,你说得对,需要进行一些更改。Core 2.2版本的构造函数应该如下所示: public MyEntityTypeGenerator(ICSharpHelper cSharpHelper) : base(cSharpHelper) { } 。 框架使用反射来查找您程序集中IDesignTimeServices的任何实现。 - Chris Peacock

7

您可以使用 DbContextWriterEntityTypeWriter 来自定义脚手架输出。

在较新的实体核心版本中,writers 重命名为:

  • DBContextWriter ==>> CSharpDbContextGenerator
  • EntityTypeWriter ==>> CSharpEntityTypeGenerator

编写一些自定义类型编写器,您可以覆盖所有内容并获取自己的代码生成器:

//HERE YOU CAN CHANGE THE WAY TYPES ARE GENERATED AND YOU CAN ADD INTERFACE OR BASE CLASS AS PARENT.
public class CustomEntitiyTypeWriter : EntityTypeWriter
{
    public CustomEntitiyTypeWriter([NotNull] CSharpUtilities cSharpUtilities)
        : base(cSharpUtilities)
    { }

    // Write Code returns generated code for class and you can raplec it with your base class
    public override string WriteCode([NotNull] EntityConfiguration entityConfiguration)
    {
        var classStr = base.WriteCode(entityConfiguration);

        var defaultStr = "public partial class " + entityConfiguration.EntityType.Name;
        var baseStr = "public partial class " + entityConfiguration.EntityType.Name + " : EntityBase";

        classStr = classStr.Replace(defaultStr, baseStr);

        return classStr;
    }      
}

在setup中声明:

public static void ConfigureDesignTimeServices(IServiceCollection services)
           => services.AddSingleton<EntityTypeWriter, CustomEntitiyTypeWriter>();

然后通过 scaffold db 命令,您可以利用 CustomDBContextWriter 对 DBContext 进行相同的操作。


@DživoJelić 是的,它在实体核心中,我更新了答案,请检查。 - Tornike Choladze
6
如果我有一个用于数据访问层的独立库,其中DbContext是使用包管理器控制台中的Scaffold-DbContext命令生成的,那么我应该如何使用它?在这种情况下,我没有任何启动文件... - Laserson
2
这些链接已经失效了吗? - duckmike
在EF Core 7.0.13中,这些类被称为什么? - undefined

5
如果您想修改实体名称(以及文件和类名称),这里有一些可能会有帮助的内容:
基于Chris Peacock的答案(和评论),您可以构建两个类来修改实体和文件名称的名称(在Core 2.2中有效)。
public class CustomEFUtilities : CSharpUtilities
{
    public override string Uniquifier(
        string proposedIdentifier, ICollection<string> existingIdentifiers)
    {
        var finalIdentifier = base.Uniquifier(proposedIdentifier, existingIdentifiers);
        
        // your changes here
        if (finalIdentifier.StartsWith("tl"))
        {
            finalIdentifier = finalIdentifier.Substring(2);
        }
        
        return finalIdentifier;
    }
}

同样地:
public class CustomEFDesignTimeServices : IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
    {
        serviceCollection.AddSingleton<ICSharpUtilities, CustomEFUtilities>();
    }
}

编辑(EF Core 3.1)

引入了一个重大更改 (https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#microsoftentityframeworkcoredesign-is-now-a-developmentdependency-package),因此您需要修改项目文件:

如果您需要引用此软件包以覆盖 EF Core 的设计时行为,则可以在项目中更新 PackageReference 项元数据。

<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.0.0">
  <PrivateAssets>all</PrivateAssets>
  <!-- Remove IncludeAssets to allow compiling against the assembly -->
  <!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
</PackageReference>

请注意,这也会影响属性(列)的名称。 - Nurp

1

目前,脚手架工具不支持您描述的场景。没有选项来自定义它们的输出,只有生成文件的位置以及是否使用Fluent API或数据注释进行配置。

EF Core是一个Code First框架。建议一旦您从现有数据库中反向工程出模型,就使用迁移来保持两者之间的同步。

话虽如此,我意识到这可能并不总是可能,这取决于团队内部分配职责的方式。在这种情况下,您可能希望考虑在EF Core的GitHub存储库上开启一个问题请求此功能:https://github.com/aspnet/EntityFramework


1
Chris Peacock的解决方案看起来应该可以工作;Tony Sneed的EntityFrameworkCore.Scaffolding.Handlebars是另一种我知道会工作的解决方案。而且,当你写这篇文章时,被接受的解决方案应该已经可以工作了。EF Core只是主要用于Code First框架。 - Auspex

1
在我的场景中,我想要用下划线作为前缀来命名所有表名。版本:Core 2.2
我不使用Id作为主键列的名称,而是将表名与Id相结合,就像这样:FoodId。这迫使我向主键添加了[Key]数据注释。


模型
示例模型生成:

using System;
using System.Collections.Generic;

namespace MyNamespace
{
    public partial class Food
    {
         public int FoodId { get; set; }
         public string Name { get; set; }
         public string Description { get; set; }
         public double Price { get; set; }
    }
}

预期结果:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; //Add

namespace MyNamespace
{
    public partial class _Food //Prefix with underscore
    {
         [Key] //Add
         public int FoodId { get; set; }
         public string Name { get; set; }
         public string Description { get; set; }
         public double Price { get; set; }
    }
}


我是如何解决这个问题的
我结合了@Chris Peacock和@DavidC的答案。

1. 首先,重命名所有实体
为了防止属性被重命名,我必须明确告诉哪些唯一标识符需要重命名,因为Uniquifier函数不会告诉你它重命名了哪种类型。

public class MyCSharpUtilities : CSharpUtilities
{
    public override string Uniquifier(string proposedIdentifier, ICollection<string> existingIdentifiers)
    {
        var finalIdentifier = base.Uniquifier(proposedIdentifier, existingIdentifiers);

        //Prefix entity names with underscore
        if (finalIdentifier == "Food" ||
            finalIdentifier == "Fruit" ||
            finalIdentifier == "Vegetables")
        {
            finalIdentifier = "_" + finalIdentifier;
        }

        return finalIdentifier;
    }
}

2. 其次,添加using语句和[Key]

public class MyEntityTypeGenerator : CSharpEntityTypeGenerator
{
    public MyEntityTypeGenerator(ICSharpHelper cSharpHelper) : base(cSharpHelper) { }   

    public override string WriteCode(IEntityType entityType, string @namespace, bool useDataAnnotations)
    {
        string code = base.WriteCode(entityType, @namespace, useDataAnnotations);           
        string entityTypeName = entityType.Name.Replace("_", "");
        string keyPropertyString = "public int " + entityTypeName + "Id { get; set; }";

        if (code.Contains(keyPropertyString))
        {
            //Add "using" for data annotation
            string oldString = "using System.Collections.Generic;" + Environment.NewLine + Environment.NewLine + "namespace MyNamespace";
            string newString = "using System.Collections.Generic;" + Environment.NewLine + "using System.ComponentModel.DataAnnotations;" + Environment.NewLine + Environment.NewLine + "namespace MyNamespace";
            code = code.Replace(oldString, newString);

            //Add [Key] data annotation
            string newKeyPropertyString = "[Key]" + Environment.NewLine + "\t\t" + keyPropertyString;
            code = code.Replace(keyPropertyString, newKeyPropertyString);
        }

        return code;
    }
}

3. 最后,在 Startup.cs 中添加这些服务。

public class CustomEFDesignTimeServices : IDesignTimeServices
{
    //Used for scaffolding database to code
    public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
    {
        serviceCollection.AddSingleton<ICSharpUtilities, MyCSharpUtilities>();
        serviceCollection.AddSingleton<ICSharpEntityTypeGenerator, MyEntityTypeGenerator>();
    }
}

ICSharpUtilities 接口在哪里? - UserControl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接