lc.*_*lc. 6 null entity-framework constraints ef-code-first
考虑实体框架代码优先的以下POCO实体:
public class Foo
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
这将生成下表:
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)
);
Run Code Online (Sandbox Code Playgroud)
现在,我理解EF的默认行为是将空字符串转换为null.因此,即使我明确地将它提供一个空字符串,我也会得到一个验证异常,这是完美的.以下代码将抛出DbEntityValidationException:
var f = new Foo { Name = "" };
context.Foos.Add(f);
context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)
但是,问题是如果我有一个直接访问数据库的外部应用程序,我可以执行以下查询并成功:
insert into dbo.Foo(Name)
values ('')
Run Code Online (Sandbox Code Playgroud)
最好的解决方案可以说是不允许任何人直接连接数据库并强制他们通过业务层.但实际上,这可能并非总是可行.特别是如果我自己通过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]");
}
}
Run Code Online (Sandbox Code Playgroud)
你可以为EF添加sql代码生成器来生成MinLength属性的这些代码,我将在这里给你一个简单的提示:
首先标记属性 MinLength
public class Test
{
public int Id { get; set; }
[MinLength(1)]
public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)添加MinLenghtAttribute到约定并提供值,即Length:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<MinLengthAttribute, int>(
"MinLength",
(property, attributes) => attributes.Single().Length));
}
Run Code Online (Sandbox Code Playgroud)
生成的迁移代码将是:
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);
Run Code Online (Sandbox Code Playgroud)覆盖SqlServerMigrationSqlGenerator使用此约定以生成constraintsql代码:
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));
}
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码包含用于生成AddColumn和CreateTable操作必须添加代码AlterColumn,DropTable并DropColumns为好.
注册新的代码生成器:
internal sealed class Configuration : DbMigrationsConfiguration<TestContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
SetSqlGenerator("System.Data.SqlClient", new ExtendedSqlGenerator());
}
}
Run Code Online (Sandbox Code Playgroud)