13

根据 Entity Framework team 的建议,我想修改 EF:CF 在生成数据库模式 (DDL) 时生成的 SQL 。

如何才能做到这一点?

我无法通过谷歌找到任何合适的东西。

4

1 回答 1

21

您可以通过在类的构造函数中调用DbMigrationsConfiguration.SetSqlGenerator()方法、传递数据库提供程序名称(例如,对于 SQL Server)和用于该数据库提供程序的实例来覆盖MigrationSqlGeneratorEntity Framework使用的那个。DbMigrationsConfiguration"System.Data.SqlClient"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");
    }
}

请注意,脚手架没有为MinLengthAttribute.

要让 EF 传达最小长度要求,您可以指定属性到列的注释约定。如该文档页面AnnotationValues所述,默认 SQL 生成器会忽略任何内容。

在 DbContext 的 OnModelCreating() 覆盖中,添加以下内容:

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

假设在链接的工作项中,您希望生成一个约束来检查修剪后的Name值是否大于 minLength(在本例中为 5)。

您可以首先创建一个自定义MigrationSqlGenerator扩展SqlServerMigrationSqlGenerator并调用 SetSqlGenerator() 来安装自定义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)
    {
        //...
    }
}

现在,这CustomSqlServerMigrationSqlGenerator覆盖了 Generate(AddColumnOperation) 方法,但只是调用了基本实现。

如果您查看的文档AddColumnOperation,您将看到两个重要的属性,Column以及Table. ColumnColumnModel由 Up(), 中的 lambda 创建的c => c.String(nullable: false, annotations: ...)

在 Generate() 方法中,您可以AnnotationValues通过Annotations.ColumnModel

要生成添加约束的 DDL,您需要生成 SQL 并调用 Statement() 方法。例如:

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

为 dbo.MyEntity.Name 指定了 minLength 5,但默认值 '' 不满足此要求。

要解决此问题,请在 Up() 方法中指定一个比最小长度更长的 defaultValue(例如"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")
                },
            }));
    }

现在,如果您重新运行Update-Database -Verbose,您将看到ALTER TABLE添加列的ALTER TABLE语句和添加约束的语句:

ALTER TABLE [dbo].[MyEntity] ADD [Name] [nvarchar](max) NOT NULL DEFAULT 'unknown'
ALTER TABLE [dbo].[MyEntity] 添加约束 [ML_dbo.MyEntity_Name] 检查 (LEN(LTRIM(RTRIM([Name]))) > 5)

另请参阅:EF6:编写您自己的代码优先迁移操作,其中展示了如何实现自定义迁移操作。

于 2015-08-17T23:16:38.860 回答