Entity Framework Core 自定义脚手架

问题描述 投票:0回答:6

我已经完成了 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; }
}
c# entity-framework-core
6个回答
7
投票

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

在新版本的实体核心编写器中重命名为:

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

在设置中声明它:

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

然后是脚手架数据库,您可以使用 CustomDBContextWriter 对 DBContext 执行相同的操作。


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

6
投票

如果您想修改实体名称(以及文件和类名称),以下内容可能会有所帮助:

根据 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/writing-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>

1
投票

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

EF Core 是一个代码优先框架。建议是,一旦您从现有数据库对模型进行了逆向工程,您就可以使用迁移来使两者从那时起保持同步。

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


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

0
投票
覆盖

CandidateNamingService

似乎是强制使用自己的命名约定的更好地方,因为您可以访问标识符所属的上下文。

我必须从许多表名中删除“tbl”前缀,并且必须修复一些其他命名问题。我无法触摸和修复原始数据库,因为还有其他应用程序。

我的(EF Core 7.0.3)解决方案如下所示:

using Microsoft.EntityFrameworkCore.Scaffolding.Internal; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; namespace MyProject.EFHelper { internal class MyCandidateNamingService : CandidateNamingService { static Dictionary<string, string> tableMapper = new Dictionary<string, string> { { "ugly_table_name1", "GoodTableName" }, { "Other_uglyTabLeName", "OtherGoodTableName" }, }; static Dictionary<string, string> columnMapper = new Dictionary<string, string> { { "bad_column_name", "GoodColumnName" }, //... }; static Dictionary<string, Dictionary<string, string>> tableColumnMapper = new Dictionary<string, Dictionary<string, string>>() { { "Table1", new Dictionary<string, string>() { {"bad_column1", "GoodColumn1" }, {"bad_column2", "GoodColumn2" } } }, { "Kundendaten", new Dictionary<string, string>() { {"Kd Nr", "IDKunde" } } } //... }; public override string GenerateCandidateIdentifier(DatabaseTable originalTable) { var result = base.GenerateCandidateIdentifier(originalTable); if (tableMapper.ContainsKey(result)) { return tableMapper[result]; } if (result.StartsWith("Tbl")) result = result.Substring(3); return result; } public override string GenerateCandidateIdentifier(DatabaseColumn originalColumn) { var result = base.GenerateCandidateIdentifier(originalColumn); if (columnMapper.ContainsKey(result)) { return columnMapper[result]; } var tableName = originalColumn.Table.Name; if (tableColumnMapper.ContainsKey(tableName)) { if (tableColumnMapper[tableName].ContainsKey(originalColumn.Name)) return tableColumnMapper[tableName][originalColumn.Name]; } return result; } }
注册您的服务:
(当您从cmdline执行

dotnet ef dbcontext scaffold...

时,会自动调用该类)

namespace MyProject.EFHelper { public class CustomEFDesignTimeServices : IDesignTimeServices { //Used for scaffolding database to code public void ConfigureDesignTimeServices(IServiceCollection serviceCollection) { serviceCollection.AddSingleton<ICandidateNamingService, MyCandidateNamingService>(); } } }
最后但并非最不重要的一点是从项目文件中删除 IncludeAssets 部分

<ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.0"> <PrivateAssets>all</PrivateAssets> <!-- Remove IncludeAssets to allow compiling against the assembly --> <!-- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> --> </PackageReference> </ItemGroup>
    
© www.soinside.com 2019 - 2024. All rights reserved.