实体框架代码中的唯一约束优先

kim*_*3er 125 entity-framework unique-constraint code-first

是否可以使用流畅的语法或属性在属性上定义唯一约束?如果没有,有哪些解决方法?

我有一个带有主键的用户类,但我想确保电子邮件地址也是唯一的.这是否可以直接编辑数据库?

解决方案(基于马特的答案)

public class MyContext : DbContext {
    public DbSet<User> Users { get; set; }

    public override int SaveChanges() {
        foreach (var item in ChangeTracker.Entries<IModel>())
            item.Entity.Modified = DateTime.Now;

        return base.SaveChanges();
    }

    public class Initializer : IDatabaseInitializer<MyContext> {
        public void InitializeDatabase(MyContext context) {
            if (context.Database.Exists() && !context.Database.CompatibleWithModel(false))
                context.Database.Delete();

            if (!context.Database.Exists()) {
                context.Database.Create();
                context.Database.ExecuteSqlCommand("alter table Users add constraint UniqueUserEmail unique (Email)");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

mat*_*mc3 61

据我所知,目前没有办法用Entity Framework做到这一点.但是,这不仅仅是一个独特约束的问题......您可能还想创建索引,检查约束,以及可能的触发器和其他构造. 这是一个简单的模式,您可以使用代码优先设置,但不可否认,它不是数据库不可知的:

public class MyRepository : DbContext {
    public DbSet<Whatever> Whatevers { get; set; }

    public class Initializer : IDatabaseInitializer<MyRepository> {
        public void InitializeDatabase(MyRepository context) {
            if (!context.Database.Exists() || !context.Database.ModelMatchesDatabase()) {
                context.Database.DeleteIfExists();
                context.Database.Create();

                context.ObjectContext.ExecuteStoreCommand("CREATE UNIQUE CONSTRAINT...");
                context.ObjectContext.ExecuteStoreCommand("CREATE INDEX...");
                context.ObjectContext.ExecuteStoreCommand("ETC...");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

另一种选择是,如果您的域模型是在数据库中插入/更新数据的唯一方法,您可以自己实现唯一性要求并将数据库从中删除.这是一个更便携的解决方案,迫使您在代码中清楚地了解业务规则,但会使数据库处于打开无效数据的状态.

  • 除非你的应用程序是单用户,否则我认为一个独特的约束是你不能单独执行代码的一件事.你可以大大降低代码中违规的可能性(通过在调用`SaveChanges()`之前检查唯一性),但是在唯一性检查时和"SaveChanges"时间之间仍然存在另一个插入/更新滑动的可能性. ()`.因此,根据应用程序的关键任务以及唯一性违规的可能性,最好将约束添加到数据库中. (23认同)
  • EntityFramework 6.1.0现在支持IndexAttribute,您基本上可以在属性之上添加它. (3认同)

Mih*_*üür 44

从EF 6.1开始,现在可以:

[Index(IsUnique = true)]
public string EmailAddress { get; set; }
Run Code Online (Sandbox Code Playgroud)

严格来说,这将为您提供唯一的索引,而不是唯一约束.对于大多数实际目的,它们是相同的.

  • 如何指定多列唯一索引? (5认同)
  • @Dave:在相应属性的属性上使用相同的索引名称([source](http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.schema.indexattribute(v = vs .113)的.aspx#remarksToggle)). (5认同)

lna*_*aie 28

与此无关,但在某些情况下可能会有所帮助.

如果您要创建一个唯一的复合索引,我们说2列将作为您的表的约束,那么从版本4.3开始,您可以使用新的迁移机制来实现它:

基本上你需要在你的一个迁移脚本中插入这样的调用:

CreateIndex("TableName", new string[2] { "Column1", "Column2" }, true, "IX_UniqueColumn1AndColumn2");
Run Code Online (Sandbox Code Playgroud)

像这样的东西:

namespace Sample.Migrations
{
    using System;
    using System.Data.Entity.Migrations;

    public partial class TableName_SetUniqueCompositeIndex : DbMigration
    {
        public override void Up()
        {
            CreateIndex("TableName", new[] { "Column1", "Column2" }, true, "IX_UniqueColumn1AndColumn2");
        }

        public override void Down()
        {
            DropIndex("TableName", new[] { "Column1", "Column2" });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 你不应该在Down()程序中有DropIndex吗?`DropIndex("TableName",new [] {"Column1","Column2"});` (2认同)

Kel*_*dge 5

在创建数据库时,我做了一个完整的hack来执行SQL.我创建自己的DatabaseInitializer并从其中一个提供的初始化程序继承.

public class MyDatabaseInitializer : RecreateDatabaseIfModelChanges<MyDbContext>
{
    protected override void Seed(MyDbContext context)
    {
        base.Seed(context);
        context.Database.Connection.StateChange += new StateChangeEventHandler(Connection_StateChange);
    }

    void Connection_StateChange(object sender, StateChangeEventArgs e)
    {
        DbConnection cnn = sender as DbConnection;

        if (e.CurrentState == ConnectionState.Open)
        {
            // execute SQL to create indexes and such
        }

        cnn.StateChange -= Connection_StateChange;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我在SQL语句中找到的唯一可以找到的地方.

这是来自CTP4.我不知道它在CTP5中是如何工作的.


小智 5

只是试图找出是否有办法做到这一点,只有我发现到目前为止自己执行的方式,我创建了一个属性,要添加到每个类,您需要提供您需要唯一的字段的名称:

    [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple=false,Inherited=true)]
public class UniqueAttribute:System.Attribute
{
    private string[] _atts;
    public string[] KeyFields
    {
        get
        {
            return _atts;
        }
    }
    public UniqueAttribute(string keyFields)
    {
        this._atts = keyFields.Split(new char[]{','}, StringSplitOptions.RemoveEmptyEntries);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在我的课上我会添加它:

[CustomAttributes.Unique("Name")]
public class Item: BasePOCO
{
    public string Name{get;set;}
    [StringLength(250)]
    public string Description { get; set; }
    [Required]
    public String Category { get; set; }
    [Required]
    public string UOM { get; set; }
    [Required]
}
Run Code Online (Sandbox Code Playgroud)

最后,我将在我的存储库中添加一个方法,在Add方法中或者像这样保存更改时:

private void ValidateDuplicatedKeys(T entity)
{
    var atts = typeof(T).GetCustomAttributes(typeof(UniqueAttribute), true);
    if (atts == null || atts.Count() < 1)
    {
        return;
    }
    foreach (var att in atts)
    {
        UniqueAttribute uniqueAtt = (UniqueAttribute)att;
        var newkeyValues = from pi in entity.GetType().GetProperties()
                            join k in uniqueAtt.KeyFields on pi.Name equals k
                            select new { KeyField = k, Value = pi.GetValue(entity, null).ToString() };
        foreach (var item in _objectSet)
        {
            var keyValues = from pi in item.GetType().GetProperties()
                            join k in uniqueAtt.KeyFields on pi.Name equals k
                            select new { KeyField = k, Value = pi.GetValue(item, null).ToString() };
            var exists = keyValues.SequenceEqual(newkeyValues);
            if (exists)
            {
                throw new System.Exception("Duplicated Entry found");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

不太好,因为我们需要依靠反射,但到目前为止这是适合我的方法!= d


Not*_*ple 5

同样在6.1中你可以使用@ mihkelmuur的流利语法版本的答案,如下所示:

Property(s => s.EmailAddress).HasColumnAnnotation(IndexAnnotation.AnnotationName,
new IndexAnnotation(
    new IndexAttribute("IX_UniqueEmail") { IsUnique = true }));
Run Code Online (Sandbox Code Playgroud)

流畅的方法不是完美的IMO,但至少现在可能.

关于亚瑟维克斯博客的更多信息http://blog.oneunicorn.com/2014/02/15/ef-6-1-creating-indexes-with-indexattribute/