首先使用 EF 代码在数据库级别停止空字符串

Stop empty strings at the database level with EF code first

首先考虑 Entity Framework Code First 的以下 POCO 实体:

public class Foo
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; }
}

这将生成以下 table:

CREATE TABLE [dbo].[Foo] (
    [Id]        INT            IDENTITY (1, 1) NOT NULL,
    [Name]      NVARCHAR (100) NOT NULL,
    CONSTRAINT [PK_dbo.Foo] PRIMARY KEY CLUSTERED ([Id] ASC)
);

现在,我了解到 EF 的默认行为是将空字符串转换为 null。所以即使我明确地给它一个空字符串,我也会得到一个验证异常,这是完美的。以下代码将抛出 DbEntityValidationException:

var f = new Foo { Name = "" };
context.Foos.Add(f);
context.SaveChanges();

但是,问题是如果我有一个直接访问数据库的外部应用程序,我可以执行以下查询并且成功:

insert into dbo.Foo(Name)
values ('')

最好的解决方案可以说是不允许任何人直接连接到数据库并强制他们通过业务层。然而,实际上这并不总是可能的。特别是如果我自己正在通过 SSIS 包导入外部数据。

我最好的理解是,应将应用程序设置为在可能的最低级别拒绝尽可能多的不良数据。在这种情况下,这意味着在数据库级别。因此,如果以老式方式创建数据库,我会添加一个约束来检查 (Name <> '') 并首先阻止脏数据被插入。

有没有办法让 EF Code First 为我生成这个约束,或者有其他方法让它在数据库级别强制执行非空字符串(最小长度为 1)——最好使用属性?或者我唯一的办法是在迁移中手动添加约束?

MinLength 属性,但它没有在数据库级别强制执行约束,我认为您应该使用迁移添加此约束。

public partial class test : DbMigration
{
    public override void Up()
    {
        Sql("ALTER TABLE [dbo].[YOUR_TABLE] ADD CONSTRAINT " + 
            "[MinLengthConstraint] CHECK (DATALENGTH([your_column]) > 0)");
    }

    public override void Down()
    {
        Sql("ALTER TABLE [dbo].[YOUR_TABLE] DROP CONSTRAINT [MinLengthConstraint]");
    }
}

您可以为 EF 添加 sql 代码生成器来为 MinLength 属性生成这些代码,我将在这里给您一个简单的提示:

  1. 首先用MinLength

    标记属性
    public class Test
    {
        public int Id { get; set; }
        [MinLength(1)]
        public string Name { get; set; }
    }
    
  2. MinLenghtAttribute 添加到约定中并提供值,即 Length :

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Conventions.Add(
            new AttributeToColumnAnnotationConvention<MinLengthAttribute, int>(
                "MinLength",
                (property, attributes) => attributes.Single().Length));
    }
    

    生成的迁移代码为:

    CreateTable(
        "dbo.Tests",
        c => new
            {
                Id = c.Int(nullable: false, identity: true),
                Name = c.String(
                     annotations: new Dictionary<string, AnnotationValues>
                     {
                         { 
                            "MinLength",
                             new AnnotationValues(oldValue: null, newValue: "1")
                         },
                     }),
             })
         .PrimaryKey(t => t.Id);
    
  3. 覆盖 SqlServerMigrationSqlGenerator 以使用此约定以生成 constraint sql 代码:

    public class ExtendedSqlGenerator : SqlServerMigrationSqlGenerator
    {
        protected override void Generate(AddColumnOperation addColumnOperation)
        {
            base.Generate(addColumnOperation);
            AddConstraint(addColumnOperation.Column, addColumnOperation.Table);
        }
    
        protected override void Generate(CreateTableOperation createTableOperation)
        {
            base.Generate(createTableOperation);
            foreach (var col in createTableOperation.Columns)
                 AddConstraint(col, createTableOperation.Name);
        }
        private void AddConstraint(ColumnModel column, string tableName)
        {
            AnnotationValues values;
            if (column.Annotations.TryGetValue("MinLength", out values))
            {
                var sql = string.Format("ALTER TABLE {0} ADD CONSTRAINT " +
                    "[MinLengthConstraint] CHECK (DATALENGTH([{1}]) >= {2})"
                    ,tableName, column.Name, values.NewValue);
                Generate(new SqlOperation(sql));
            }
       }
    }
    

    上面的代码包含 AddColumnCreateTable 操作的生成,您还必须添加 AlterColumnDropTableDropColumns 的代码。

  4. 注册新代码生成器:

    internal sealed class Configuration : DbMigrationsConfiguration<TestContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            SetSqlGenerator("System.Data.SqlClient", new ExtendedSqlGenerator());
        }
    }