我希望在生成数据库架构(DDL)时修改由EF:CF生成的SQL,如suggested by the Entity Framework team。
如何做到这一点?
我无法通过Google找到合适的内容。
答案 0 :(得分:18)
您可以通过调用DbMigrationsConfiguration
类的构造函数中的MigrationSqlGenerator
方法来覆盖Entity Framework使用的DbMigrationsConfiguration.SetSqlGenerator(),并传递数据库提供程序名称(例如{{1} (对于SQL Server),以及用于该数据库提供程序的"System.Data.SqlClient"
实例。
考虑您链接到the work item的示例:
MigrationSqlGenerator
假设已生成public class MyEntity
{
public int Id { get; set; }
[Required]
[MinLength(5)]
public string Name { get; set; }
}
的表,并使用MyEntity
命令添加Add-Migration
字段。
默认情况下,脚手架迁移是:
Name
请注意,脚手架没有为public partial class AddMyEntity_Name : DbMigration
{
public override void Up()
{
AddColumn("dbo.MyEntity", "Name", c => c.String(nullable: false));
}
public override void Down()
{
DropColumn("dbo.MyEntity", "Name");
}
}
生成任何内容。
要让EF传达最小长度要求,您可以specify an attribute-to-column annotation convention。正如该文档页面中所提到的,默认SQL生成器会忽略任何AnnotationValues
。
在DbContext的OnModelCreating()覆盖中,添加以下内容:
MinLengthAttribute
添加后,您可以通过运行modelBuilder.Conventions.Add(new AttributeToColumnAnnotationConvention<MinLengthAttribute, Int32>("minLength", (property, attributes) => attributes.Single().Length));
重新生成脚手架迁移。现在脚手架迁移是:
Add-Migration -Force AddMyEntity_Name
假设在链接的工作项中,您希望生成约束以检查修剪的public partial class AddMyEntity_Name : DbMigration
{
public override void Up()
{
AddColumn("dbo.MyEntity", "Name", c => c.String(nullable: false,
annotations: new Dictionary<string, AnnotationValues>
{
{
"minLength",
new AnnotationValues(oldValue: null, newValue: "5")
},
}));
}
public override void Down()
{
DropColumn("dbo.MyEntity", "Name",
removedAnnotations: new Dictionary<string, object>
{
{ "minLength", "5" },
});
}
}
值是否大于minLength(在这种情况下为5)。
您可以首先创建扩展Name
的自定义MigrationSqlGenerator
并调用SetSqlGenerator()来安装自定义SqlServerMigrationSqlGenerator
:
MigrationSqlGenerator
现在,这个internal class CustomSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(AddColumnOperation addColumnOperation)
{
base.Generate(addColumnOperation);
}
}
internal sealed class Configuration : DbMigrationsConfiguration<DataContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
SetSqlGenerator("System.Data.SqlClient", new CustomSqlServerMigrationSqlGenerator());
}
protected override void Seed(DataContext context)
{
//...
}
}
会覆盖Generate(AddColumnOperation)方法,但只是调用基本实现。
如果查看the documentation of AddColumnOperation
,您会看到两个重要属性,CustomSqlServerMigrationSqlGenerator
和Column
。 Table
是由{(1}}中的lambda创建的ColumnModel
。
在Generate()方法中,您可以通过Column
的{{1}}属性访问自定义c => c.String(nullable: false, annotations: ...)
。
要生成添加约束的DDL,您需要生成SQL并调用Statement()方法。例如:
AnnotationValues
如果您运行Annotations
,您会看到ColumnModel
生成的异常:
minLength 5 specified for dbo.MyEntity.Name, but the default value, '', does not satisfy this requirement.
要解决此问题,请在Up()方法中指定一个长度超过最小长度的defaultValue(例如internal class CustomSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(AddColumnOperation addColumnOperation)
{
base.Generate(addColumnOperation);
var column = addColumnOperation.Column;
if (column.Type == System.Data.Entity.Core.Metadata.Edm.PrimitiveTypeKind.String)
{
var annotations = column.Annotations;
AnnotationValues minLengthValues;
if (annotations.TryGetValue("minLength", out minLengthValues))
{
var minLength = Convert.ToInt32(minLengthValues.NewValue);
if (minLength > 0)
{
if (Convert.ToString(column.DefaultValue).Trim().Length < minLength)
{
throw new ArgumentException(String.Format("minLength {0} specified for {1}.{2}, but the default value, '{3}', does not satisfy this requirement.", minLength, addColumnOperation.Table, column.Name, column.DefaultValue));
}
using (var writer = new StringWriter())
{
writer.Write("ALTER TABLE ");
writer.Write(Name(addColumnOperation.Table));
writer.Write(" ADD CONSTRAINT ");
writer.Write(Quote("ML_" + addColumnOperation.Table + "_" + column.Name));
writer.Write(" CHECK (LEN(LTRIM(RTRIM({0}))) > {1})", Quote(column.Name), minLength);
Statement(writer.ToString());
}
}
}
}
}
}
):
Update-Database -Verbose
现在,如果您重新运行CustomSqlServerMigrationSqlGenerator
,您将看到添加列的"unknown"
语句和添加约束的 public override void Up()
{
AddColumn("dbo.MyEntity", "Name", c => c.String(nullable: false, defaultValue: "unknown",
annotations: new Dictionary<string, AnnotationValues>
{
{
"minLength",
new AnnotationValues(oldValue: null, newValue: "5")
},
}));
}
语句:
ALTER TABLE [dbo].[MyEntity] ADD [Name] [nvarchar](max) NOT NULL DEFAULT 'unknown' ALTER TABLE [dbo].[MyEntity] ADD CONSTRAINT [ML_dbo.MyEntity_Name] CHECK (LEN(LTRIM(RTRIM([Name]))) > 5)
另请参阅:EF6: Writing Your Own Code First Migration Operations,其中显示了如何实施自定义迁移操作。