entity framework代码中的唯一约束优先

是否有可能使用stream利的语法或属性定义一个属性的唯一约束? 如果没有,解决方法是什么?

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

解决scheme(基于马特的回答)

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

据我所知,目前没有办法与Entity Framework做到这一点。 然而,这不仅仅是一个独特约束的问题……你可能想要创build索引,检查约束以及可能的触发器和其他结构。 这是一个简单的模式,你可以使用你的代码优先的设置,虽然承认它不是数据库不可知的:

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

另一个select是,如果您的域模型是在数据库中插入/更新数据的唯一方法,那么您可以自己实现唯一性要求,并使数据库不在其中。 这是一个更便于使用的解决scheme,并强制您清楚您的代码中的业务规则,但将数据库打开,以获取无效的数据。

从EF 6.1开始,现在有可能:

 [Index(IsUnique = true)] public string EmailAddress { get; set; } 

严格来说,这将给你一个独特的索引,而不是唯一的约束。 对于大多数实际的目的, 他们是一样的 。

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

如果你想创build一个唯一的组合索引,比方说2列将作为你的表的约束,那么从4.3版开始,你可以使用新的迁移机制来实现它:

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

 CreateIndex("TableName", new string[2] { "Column1", "Column2" }, true, "IX_UniqueColumn1AndColumn2"); 

类似的东西:

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

在创build数据库时,我会执行一个完整的hack来执行SQL。 我创build了我自己的DatabaseInitializer,并从提供的初始化程序之一inheritance。

 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; } } 

这是我能find的在我的SQL语句中唯一的地方。

这是来自CTP4。 我不知道CTP5是如何工作的。

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

  [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); } } 

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

 [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] } 

最后,我将在存储库中添加一个方法,在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"); } } } } 

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

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

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

stream利的方法不是完美的国际海事组织,但至less现在是可能的。

亚瑟·维克斯博客更多细节http://blog.oneunicorn.com/2014/02/15/ef-6-1-creating-indexes-with-indexattribute/

使用EF5 Code First Migrations进行Visual Basic的简单方法

公共类样本

  Public Property SampleId As Integer <Required> <MinLength(1),MaxLength(200)> Public Property Code() As String 

末class

MaxLength属性对于stringtypes的唯一索引非常重要

运行cmd:update-database -verbose

运行cmd后:add-migration 1

在生成的文件中

 Public Partial Class _1 Inherits DbMigration Public Overrides Sub Up() CreateIndex("dbo.Sample", "Code", unique:=True, name:="IX_Sample_Code") End Sub Public Overrides Sub Down() 'DropIndex if you need it End Sub End Class 

与Tobias Schittkowski的答案类似,但C#有能力在常量中有多个字段。

要使用此function,只需将[Unique]放置在您希望唯一的任何字段上。 对于string,你将不得不做类似的事情(注意MaxLength属性):

 [Unique] [MaxLength(450)] // nvarchar(450) is max allowed to be in a key public string Name { get; set; } 

因为默认的string字段是nvarchar(max),并且不允许在一个键中使用。

对于约束中的多个字段,可以这样做:

 [Unique(Name="UniqueValuePairConstraint", Position=1)] public int Value1 { get; set; } [Unique(Name="UniqueValuePairConstraint", Position=2)] public int Value2 { get; set; } 

首先,UniqueAttribute:

 /// <summary> /// The unique attribute. Use to mark a field as unique. The /// <see cref="DatabaseInitializer"/> looks for this attribute to /// create unique constraints in tables. /// </summary> internal class UniqueAttribute : Attribute { /// <summary> /// Gets or sets the name of the unique constraint. A name will be /// created for unnamed unique constraints. You must name your /// constraint if you want multiple fields in the constraint. If your /// constraint has only one field, then this property can be ignored. /// </summary> public string Name { get; set; } /// <summary> /// Gets or sets the position of the field in the constraint, lower /// numbers come first. The order is undefined for two fields with /// the same position. The default position is 0. /// </summary> public int Position { get; set; } } 

然后,包含一个有用的扩展来从一个types获取数据库表名:

 public static class Extensions { /// <summary> /// Get a table name for a class using a DbContext. /// </summary> /// <param name="context"> /// The context. /// </param> /// <param name="type"> /// The class to look up the table name for. /// </param> /// <returns> /// The table name; null on failure; /// </returns> /// <remarks> /// <para> /// Like: /// <code> /// DbContext context = ...; /// string table = context.GetTableName&lt;Foo&gt;(); /// </code> /// </para> /// <para> /// This code uses ObjectQuery.ToTraceString to generate an SQL /// select statement for an entity, and then extract the table /// name from that statement. /// </para> /// </remarks> public static string GetTableName(this DbContext context, Type type) { return ((IObjectContextAdapter)context) .ObjectContext.GetTableName(type); } /// <summary> /// Get a table name for a class using an ObjectContext. /// </summary> /// <param name="context"> /// The context. /// </param> /// <param name="type"> /// The class to look up the table name for. /// </param> /// <returns> /// The table name; null on failure; /// </returns> /// <remarks> /// <para> /// Like: /// <code> /// ObjectContext context = ...; /// string table = context.GetTableName&lt;Foo&gt;(); /// </code> /// </para> /// <para> /// This code uses ObjectQuery.ToTraceString to generate an SQL /// select statement for an entity, and then extract the table /// name from that statement. /// </para> /// </remarks> public static string GetTableName(this ObjectContext context, Type type) { var genericTypes = new[] { type }; var takesNoParameters = new Type[0]; var noParams = new object[0]; object objectSet = context.GetType() .GetMethod("CreateObjectSet", takesNoParameters) .MakeGenericMethod(genericTypes) .Invoke(context, noParams); var sql = (string)objectSet.GetType() .GetMethod("ToTraceString", takesNoParameters) .Invoke(objectSet, noParams); Match match = Regex.Match(sql, @"FROM\s+(.*)\s+AS", RegexOptions.IgnoreCase); return match.Success ? match.Groups[1].Value : null; } } 

那么,数据库初始化程序:

 /// <summary> /// The database initializer. /// </summary> public class DatabaseInitializer : IDatabaseInitializer<PedContext> { /// <summary> /// Initialize the database. /// </summary> /// <param name="context"> /// The context. /// </param> public void InitializeDatabase(FooContext context) { // if the database has changed, recreate it. if (context.Database.Exists() && !context.Database.CompatibleWithModel(false)) { context.Database.Delete(); } if (!context.Database.Exists()) { context.Database.Create(); // Look for database tables in the context. Tables are of // type DbSet<>. foreach (PropertyInfo contextPropertyInfo in context.GetType().GetProperties()) { var contextPropertyType = contextPropertyInfo.PropertyType; if (contextPropertyType.IsGenericType && contextPropertyType.Name.Equals("DbSet`1")) { Type tableType = contextPropertyType.GetGenericArguments()[0]; var tableName = context.GetTableName(tableType); foreach (var uc in UniqueConstraints(tableType, tableName)) { context.Database.ExecuteSqlCommand(uc); } } } // this is a good place to seed the database context.SaveChanges(); } } /// <summary> /// Get a list of TSQL commands to create unique constraints on the given /// table. Looks through the table for fields with the UniqueAttribute /// and uses those and the table name to build the TSQL strings. /// </summary> /// <param name="tableClass"> /// The class that expresses the database table. /// </param> /// <param name="tableName"> /// The table name in the database. /// </param> /// <returns> /// The list of TSQL statements for altering the table to include unique /// constraints. /// </returns> private static IEnumerable<string> UniqueConstraints( Type tableClass, string tableName) { // the key is the name of the constraint and the value is a list // of (position,field) pairs kept in order of position - the entry // with the lowest position is first. var uniqueConstraints = new Dictionary<string, List<Tuple<int, string>>>(); foreach (PropertyInfo entityPropertyInfo in tableClass.GetProperties()) { var unique = entityPropertyInfo.GetCustomAttributes(true) .OfType<UniqueAttribute>().FirstOrDefault(); if (unique != null) { string fieldName = entityPropertyInfo.Name; // use the name field in the UniqueAttribute or create a // name if none is given string constraintName = unique.Name ?? string.Format( "constraint_{0}_unique_{1}", tableName .Replace("[", string.Empty) .Replace("]", string.Empty) .Replace(".", "_"), fieldName); List<Tuple<int, string>> constraintEntry; if (!uniqueConstraints.TryGetValue( constraintName, out constraintEntry)) { uniqueConstraints.Add( constraintName, new List<Tuple<int, string>> { new Tuple<int, string>( unique.Position, fieldName) }); } else { // keep the list of fields in order of position for (int i = 0; ; ++i) { if (i == constraintEntry.Count) { constraintEntry.Add( new Tuple<int, string>( unique.Position, fieldName)); break; } if (unique.Position < constraintEntry[i].Item1) { constraintEntry.Insert( i, new Tuple<int, string>( unique.Position, fieldName)); break; } } } } } return uniqueConstraints.Select( uc => string.Format( "ALTER TABLE {0} ADD CONSTRAINT {1} UNIQUE ({2})", tableName, uc.Key, string.Join(",", uc.Value.Select(v => v.Item2)))); } } 

如果您在DbContext类中重写了ValidateEntity方法,那么也可以将逻辑放在那里。 这样做的好处是您可以完全访问所有的DbSets。 这是一个例子:

 using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.ModelConfiguration.Conventions; using System.Data.Entity.Validation; using System.Linq; namespace MvcEfClient.Models { public class Location { [Key] public int LocationId { get; set; } [Required] [StringLength(50)] public string Name { get; set; } } public class CommitteeMeetingContext : DbContext { protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) { List<DbValidationError> validationErrors = new List<DbValidationError>(); // Check for duplicate location names if (entityEntry.Entity is Location) { Location location = entityEntry.Entity as Location; // Select the existing location var existingLocation = (from l in Locations where l.Name == location.Name && l.LocationId != location.LocationId select l).FirstOrDefault(); // If there is an existing location, throw an error if (existingLocation != null) { validationErrors.Add(new DbValidationError("Name", "There is already a location with the name '" + location.Name + "'")); return new DbEntityValidationResult(entityEntry, validationErrors); } } return base.ValidateEntity(entityEntry, items); } public DbSet<Location> Locations { get; set; } } } 

我通过反思解决了这个问题(抱歉,伙计们,VB.Net …)

首先定义一个属性UniqueAttribute:

 <AttributeUsage(AttributeTargets.Property, AllowMultiple:=False, Inherited:=True)> _ Public Class UniqueAttribute Inherits Attribute End Class 

然后,增强你的模型

 <Table("Person")> _ Public Class Person <Unique()> _ Public Property Username() As String End Class 

最后,创build一个自定义的DatabaseInitializer(在我的版本中,只有在debugging模式下,我才能重新创buildDB更改)。 在这个DatabaseInitializer中,索引是基于唯一属性自动创build的:

 Imports System.Data.Entity Imports System.Reflection Imports System.Linq Imports System.ComponentModel.DataAnnotations Public Class DatabaseInitializer Implements IDatabaseInitializer(Of DBContext) Public Sub InitializeDatabase(context As DBContext) Implements IDatabaseInitializer(Of DBContext).InitializeDatabase Dim t As Type Dim tableName As String Dim fieldName As String If Debugger.IsAttached AndAlso context.Database.Exists AndAlso Not context.Database.CompatibleWithModel(False) Then context.Database.Delete() End If If Not context.Database.Exists Then context.Database.Create() For Each pi As PropertyInfo In GetType(DBContext).GetProperties If pi.PropertyType.IsGenericType AndAlso _ pi.PropertyType.Name.Contains("DbSet") Then t = pi.PropertyType.GetGenericArguments(0) tableName = t.GetCustomAttributes(True).OfType(Of TableAttribute).FirstOrDefault.Name For Each piEntity In t.GetProperties If piEntity.GetCustomAttributes(True).OfType(Of Model.UniqueAttribute).Any Then fieldName = piEntity.Name context.Database.ExecuteSqlCommand("ALTER TABLE " & tableName & " ADD CONSTRAINT con_Unique_" & tableName & "_" & fieldName & " UNIQUE (" & fieldName & ")") End If Next End If Next End If End Sub End Class 

也许这有助于…

如果您使用EF5,仍然有这个问题,下面的解决scheme解决了我。

我使用代码第一种方法,因此把:

 this.Sql("CREATE UNIQUE NONCLUSTERED INDEX idx_unique_username ON dbo.Users(Username) WHERE Username IS NOT NULL;"); 

在迁移脚本中做得很好。 它也允许NULL值!

使用EF Code First方法,可以使用以下技术来实现基于属性的唯一约束支持。

创build一个标记属性

 [AttributeUsage(AttributeTargets.Property)] public class UniqueAttribute : System.Attribute { } 

标记您希望在实体上唯一的属性,例如

 [Unique] public string EmailAddress { get; set; } 

创build数据库初始化程序或使用现有程序创build唯一约束

 public class DbInitializer : IDatabaseInitializer<DbContext> { public void InitializeDatabase(DbContext db) { if (db.Database.Exists() && !db.Database.CompatibleWithModel(false)) { db.Database.Delete(); } if (!db.Database.Exists()) { db.Database.Create(); CreateUniqueIndexes(db); } } private static void CreateUniqueIndexes(DbContext db) { var props = from p in typeof(AppDbContext).GetProperties() where p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>) select p; foreach (var prop in props) { var type = prop.PropertyType.GetGenericArguments()[0]; var fields = from p in type.GetProperties() where p.GetCustomAttributes(typeof(UniqueAttribute), true).Any() select p.Name; foreach (var field in fields) { const string sql = "ALTER TABLE dbo.[{0}] ADD CONSTRAINT" + " [UK_dbo.{0}_{1}] UNIQUE ([{1}])"; var command = String.Format(sql, type.Name, field); db.Database.ExecuteSqlCommand(command); } } } } 

设置你的数据库上下文以在启动代码中使用这个初始化器(例如在main()Application_Start()

 Database.SetInitializer(new DbInitializer()); 

解决scheme与mheyman类似,简化了不支持组合键。 与EF 5.0+一起使用。

我今天面对这个问题,最后我解决了这个问题。 我不知道是否是一个正确的方法,但至less我可以继续:

 public class Person : IValidatableObject { public virtual int ID { get; set; } public virtual string Name { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { var field = new[] { "Name" }; // Must be the same as the property PFContext db = new PFContext(); Person person = validationContext.ObjectInstance as Person; var existingPerson = db.Persons.FirstOrDefault(a => a.Name == person.Name); if (existingPerson != null) { yield return new ValidationResult("That name is already in the db", field); } } } 

使用独特的属性validation器。

 protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) { var validation_state = base.ValidateEntity(entityEntry, items); if (entityEntry.Entity is User) { var entity = (User)entityEntry.Entity; var set = Users; //check name unique if (!(set.Any(any_entity => any_entity.Name == entity.Name))) {} else { validation_state.ValidationErrors.Add(new DbValidationError("Name", "The Name field must be unique.")); } } return validation_state; } 

在相同的数据库事务中不调用ValidateEntity 。 因此,数据库中可能存在与其他实体的竞争条件。 你必须破解EF来强制执行SaveChanges (因此ValidateEntity )的事务。 DBContext不能直接打开连接,但ObjectContext可以。

 using (TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required)) { ((IObjectContextAdapter)data_context).ObjectContext.Connection.Open(); data_context.SaveChanges(); transaction.Complete(); } 

由于没有内置的注释,我想出了一个解决方法。 请参阅此链接更多信息https://stackoverflow.com/a/16496291/1873113

阅读完这个问题之后,我有一个自己的问题,试图将属性指定为MihkelMüür's , Tobias Schittkowski's以及mheyman的答案:将Map Entity Framework代码属性映射到数据库列(CSpace到SSpace)

我终于到了这个答案,它可以将标量和导航属性映射到数据库列,并创build一个唯一的索引在属性上指定的特定序列。 此代码假定您已经使用Sequence属性实现了UniqueAttribute,并将其应用于表示实体唯一键(除主键)之外的EF实体类属性。

注意:此代码依赖于EF版本6.1(或更高版本),其中公开的EntityContainerMapping在以前的版本中不可用。

 Public Sub InitializeDatabase(context As MyDB) Implements IDatabaseInitializer(Of MyDB).InitializeDatabase If context.Database.CreateIfNotExists Then Dim ws = DirectCast(context, System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext.MetadataWorkspace Dim oSpace = ws.GetItemCollection(Core.Metadata.Edm.DataSpace.OSpace) Dim entityTypes = oSpace.GetItems(Of EntityType)() Dim entityContainer = ws.GetItems(Of EntityContainer)(DataSpace.CSpace).Single() Dim entityMapping = ws.GetItems(Of EntityContainerMapping)(DataSpace.CSSpace).Single.EntitySetMappings Dim associations = ws.GetItems(Of EntityContainerMapping)(DataSpace.CSSpace).Single.AssociationSetMappings For Each setType In entityTypes Dim cSpaceEntitySet = entityContainer.EntitySets.SingleOrDefault( _ Function(t) t.ElementType.Name = setType.Name) If cSpaceEntitySet Is Nothing Then Continue For ' Derived entities will be skipped Dim sSpaceEntitySet = entityMapping.Single(Function(t) t.EntitySet Is cSpaceEntitySet) Dim tableInfo As MappingFragment If sSpaceEntitySet.EntityTypeMappings.Count = 1 Then tableInfo = sSpaceEntitySet.EntityTypeMappings.Single.Fragments.Single Else ' Select only the mapping (esp. PropertyMappings) for the base class tableInfo = sSpaceEntitySet.EntityTypeMappings.Where(Function(m) m.IsOfEntityTypes.Count _ = 1 AndAlso m.IsOfEntityTypes.Single.Name Is setType.Name).Single().Fragments.Single End If Dim tableName = If(tableInfo.StoreEntitySet.Table, tableInfo.StoreEntitySet.Name) Dim schema = tableInfo.StoreEntitySet.Schema Dim clrType = Type.GetType(setType.FullName) Dim uniqueCols As IList(Of String) = Nothing For Each propMap In tableInfo.PropertyMappings.OfType(Of ScalarPropertyMapping)() Dim clrProp = clrType.GetProperty(propMap.Property.Name) If Attribute.GetCustomAttribute(clrProp, GetType(UniqueAttribute)) IsNot Nothing Then If uniqueCols Is Nothing Then uniqueCols = New List(Of String) uniqueCols.Add(propMap.Column.Name) End If Next For Each navProp In setType.NavigationProperties Dim clrProp = clrType.GetProperty(navProp.Name) If Attribute.GetCustomAttribute(clrProp, GetType(UniqueAttribute)) IsNot Nothing Then Dim assocMap = associations.SingleOrDefault(Function(a) _ a.AssociationSet.ElementType.FullName = navProp.RelationshipType.FullName) Dim sProp = assocMap.Conditions.Single If uniqueCols Is Nothing Then uniqueCols = New List(Of String) uniqueCols.Add(sProp.Column.Name) End If Next If uniqueCols IsNot Nothing Then Dim propList = uniqueCols.ToArray() context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX IX_" & tableName & "_" & String.Join("_", propList) _ & " ON " & schema & "." & tableName & "(" & String.Join(",", propList) & ")") End If Next End If End Sub 

对于那些使用代码优先configuration的用户,也可以使用IndexAttribute对象作为ColumnAnnotation,并将其IsUnique属性设置为true。

例如:

 var indexAttribute = new IndexAttribute("IX_name", 1) {IsUnique = true}; Property(i => i.Name).HasColumnAnnotation("Index",new IndexAnnotation(indexAttribute)); 

这将在Name列上创build一个名为IX_name的唯一索引。

对不起,迟到的答复,但我发现很好,与你一起来

我已经在代码项目上发布了这个

一般来说,它取决于你放在类上的属性来生成你唯一的索引

Interesting Posts