我已经完成了 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; }
}
您可以使用 DbContextWriter 和 EntityTypeWriter 自定义脚手架输出。
编写一些自定义类型编写器,您可以覆盖所有内容,您将获得自己的代码生成器:
//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 执行相同的操作。
@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>();
}
}
如果您想修改实体名称(以及文件和类名称),以下内容可能会有所帮助:
根据 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>
目前脚手架工具不支持您描述的场景。没有自定义其输出的选项,只有生成文件的位置以及是否使用 Fluent API 或数据注释进行配置。
EF Core 是一个代码优先框架。建议是,一旦您从现有数据库对模型进行了逆向工程,您就可以使用迁移来使两者从那时起保持同步。
话虽如此,我意识到这并不总是可行,具体取决于团队内的职责分配方式。在这种情况下,您可能需要考虑在 EF Core 的 GitHub 存储库上提出请求此功能的问题:https://github.com/aspnet/EntityFramework。
在我的场景中,我想为所有表名添加下划线前缀。版本: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>();
}
}
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>