实体框架代码中的唯一约束
Posted
技术标签:
【中文标题】实体框架代码中的唯一约束【英文标题】:Unique Constraint in Entity Framework Code First 【发布时间】:2011-05-23 17:18:56 【问题描述】:问题
是否可以使用流畅的语法或属性定义属性的唯一约束?如果没有,有什么解决方法?
我有一个带有主键的用户类,但我想确保电子邮件地址也是唯一的。如果不直接编辑数据库,这可能吗?
解决方案(基于马特的回答)
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)");
【问题讨论】:
请记住,这样做会将您的应用程序限制为仅接受该确切语法的数据库 - 在本例中为 SQL Server。如果您使用 Oracle 提供程序运行您的应用程序,它将失败。 在那种情况下,我只需要创建一个新的 Initializer 类,但这是一个有效的点。 查看这篇文章:ValidationAttribute that validates a unique field against its fellow rows in the database,解决方案的目标是ObjectContext
或DbContext
。
是的,现在支持since EF 6.1。
【参考方案1】:
据我所知,目前无法使用 Entity Framework 执行此操作。但是,这不仅仅是唯一约束的问题……您可能还想创建索引、检查约束,还可能需要触发器和其他构造。 Here's a simple pattern you can use 使用您的代码优先设置,尽管不可否认它与数据库无关:
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...");
另一种选择是,如果您的域模型是在数据库中插入/更新数据的唯一方法,您可以自己实现唯一性要求并将数据库排除在外。这是一种更便携的解决方案,它会强制您清楚代码中的业务规则,但会使您的数据库对无效数据开放。
【讨论】:
我喜欢我的数据库像鼓一样紧凑,逻辑在业务层中复制。您的答案仅适用于 CTP4,但让我走上了正轨,我在原始问题下方提供了与 CTP5 兼容的解决方案。非常感谢! 除非您的应用是单用户应用,否则我相信唯一约束是您无法仅通过代码强制执行的一件事。您可以显着降低代码违规的可能性(通过在调用SaveChanges()
之前检查唯一性),但在唯一性检查时间和SaveChanges()
时间之间仍有可能再次插入/更新。因此,根据应用程序的任务关键程度以及违反唯一性的可能性,最好将约束添加到数据库中。
您必须让您的唯一性检查与您的 SaveChanges 成为同一事务的一部分。假设您的数据库是酸兼容的,您绝对应该能够以这种方式强制执行唯一性。现在 EF 是否允许您以这种方式正确管理事务生命周期是另一个问题。
@mattmc3 这取决于您的事务隔离级别。只有serializable isolation level
(或自定义表锁定,呃)实际上可以让您保证代码的唯一性。但出于性能原因,大多数人不使用serializable isolation level
。 MS Sql Server 中的默认值为read committed
。请参阅从以下位置开始的 4 部分系列:michaeljswart.com/2010/03/…
EntityFramework 6.1.0 现在支持 IndexAttribute,您基本上可以将它添加到属性之上。【参考方案2】:
从 EF 6.1 开始,现在可以:
[Index(IsUnique = true)]
public string EmailAddress get; set;
严格来说,这将为您提供唯一索引而不是唯一约束。对于大多数实际用途they are the same。
【讨论】:
@Dave:只需在各个属性的属性上使用相同的索引名称(source)。 请注意,这会创建一个唯一索引,而不是唯一的约束。虽然几乎相同,但它们并不完全相同(据我所知,独特的约束可以用作 FK 的目标)。对于约束,您需要执行 SQL。 (在最后一条评论之后)其他消息来源表明此限制已在较新版本的 SQL Server 中删除......但 BOL 并不完全一致。 @Richard:基于属性的唯一约束也是可能的(参见my second answer),虽然不是开箱即用的。 @exSnake:自 SQL Server 2008 起,唯一索引默认支持每列单个 NULL 值。如果需要支持多个 NULL,则需要过滤索引,请参阅another question。【参考方案3】:与此无关,但在某些情况下可能会有所帮助。
如果您希望在假设 2 列上创建一个唯一的复合索引,作为表的约束,那么从 4.3 版开始,您可以使用新的迁移机制来实现它:
http://msdn.microsoft.com/en-us/library/hh770484(v=vs.103).aspx http://blogs.msdn.com/b/adonet/archive/2012/02/09/ef-4-3-code-based-migrations-walkthrough.aspx基本上,您需要在其中一个迁移脚本中插入这样的调用:
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" );
【讨论】:
很高兴看到 EF 有 Rails 风格的迁移。现在要是我能在 Mono 上运行它就好了。 你不应该在 Down() 过程中也有一个 DropIndex 吗?DropIndex("TableName", new[] "Column1", "Column2" );
【参考方案4】:
我做了一个完整的破解来在创建数据库时执行 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;
这是我能找到的唯一插入我的 SQL 语句的地方。
这是来自 CTP4。我不知道它在 CTP5 中是如何工作的。
【讨论】:
谢谢凯利!我不知道那个事件处理程序。我的最终解决方案将 SQL 放在 InitializeDatabase 方法中。【参考方案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);
然后在我的课堂上添加它:
[CustomAttributes.Unique("Name")]
public class Item: BasePOCO
public string Nameget;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】:同样在 6.1 中,您可以像这样使用@mihkelmuur 答案的流畅语法版本:
Property(s => s.EmailAddress).HasColumnAnnotation(IndexAnnotation.AnnotationName,
new IndexAnnotation(
new IndexAttribute("IX_UniqueEmail") IsUnique = true ));
流畅的方法不是完美的IMO,但至少现在是可能的。
更多关于亚瑟维克斯博客http://blog.oneunicorn.com/2014/02/15/ef-6-1-creating-indexes-with-indexattribute/
【讨论】:
【参考方案7】:在 Visual Basic 中使用 EF5 Code First 迁移的简单方法
公开课示例
Public Property SampleId As Integer
<Required>
<MinLength(1),MaxLength(200)>
Public Property Code() As String
结束类
属性MaxLength对于字符串类型的唯一索引很重要
运行 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
【讨论】:
这实际上是比自定义 DB 初始化程序更合适的答案。【参考方案8】:类似于 Tobias Schittkowski 的回答,但 C# 并且能够在约束中包含多个字段。
要使用它,只需在您希望唯一的任何字段上放置一个 [Unique]。对于字符串,您必须执行类似的操作(注意 MaxLength 属性):
[Unique]
[MaxLength(450)] // nvarchar(450) is max allowed to be in a key
public string Name get; set;
因为默认字符串字段是 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;
然后,包含一个有用的扩展来从类型中获取数据库表名:
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<Foo>();
/// </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<Foo>();
/// </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))));
【讨论】:
【参考方案9】:我通过反思解决了这个问题(对不起,伙计们,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
最后,创建一个自定义 DatabaseInitializer(在我的版本中,只有在调试模式下,我才会在 DB 更改时重新创建 DB...)。在这个 DatabaseInitializer 中,索引是根据 Unique-Attributes 自动创建的:
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
也许这有帮助...
【讨论】:
【参考方案10】:Fluent Api 解决方案:
modelBuilder.Entity<User>(entity =>
entity.HasIndex(e => e.UserId)
.HasName("IX_User")
.IsUnique();
entity.HasAlternateKey(u => u.Email);
entity.HasIndex(e => e.Email)
.HasName("IX_Email")
.IsUnique();
);
【讨论】:
【参考方案11】:如果您在 DbContext 类中覆盖 ValidateEntity 方法,您也可以将逻辑放在那里。这样做的好处是您可以完全访问所有 DbSet。这是一个例子:
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;
【讨论】:
【参考方案12】:如果您使用的是 EF5 并且仍然有这个问题,下面的解决方案为我解决了。
我使用的是代码优先的方法,因此:
this.Sql("CREATE UNIQUE NONCLUSTERED INDEX idx_unique_username ON dbo.Users(Username) WHERE Username IS NOT NULL;");
在迁移脚本中做得很好。它还允许 NULL 值!
【讨论】:
【参考方案13】:使用 EF Code First 方法,可以使用以下技术实现基于属性的唯一约束支持。
创建标记属性
[AttributeUsage(AttributeTargets.Property)]
public class UniqueAttribute : System.Attribute
标记您希望在实体上具有唯一性的属性,例如
[Unique]
public string EmailAddress get; set;
创建一个数据库初始化器或使用现有的初始化器来创建唯一约束
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());
解决方案类似于 mheyman 的解决方案,简化为不支持复合键。与 EF 5.0+ 一起使用。
【讨论】:
【参考方案14】:我今天遇到了这个问题,最后我能够解决它。我不知道是否是正确的方法,但至少我可以继续前进:
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);
【讨论】:
【参考方案15】:使用唯一的属性验证器。
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();
【讨论】:
【参考方案16】:根据http://blogs.msdn.com/b/adonet/archive/2014/02/11/ef-6-1-0-beta-1-available.aspx,EF 6.1 将有一个 IndexAttribute 来帮助我们。
【讨论】:
【参考方案17】:阅读此问题后,我在尝试实现一个属性以将属性指定为唯一键(如Mihkel Müür's、Tobias Schittkowski's 和mheyman's)的过程中遇到了自己的问题,答案建议:Map Entity Framework code properties to database columns (CSpace to SSpace)
我终于得到了这个答案,它可以将标量和导航属性映射到数据库列,并以属性上指定的特定顺序创建唯一索引。此代码假定您已实现具有 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
【讨论】:
【参考方案18】:对于那些使用代码优先配置的用户,您还可以将 IndexAttribute 对象用作 ColumnAnnotation 并将其 IsUnique 属性设置为 true。
例如:
var indexAttribute = new IndexAttribute("IX_name", 1) IsUnique = true;
Property(i => i.Name).HasColumnAnnotation("Index",new IndexAnnotation(indexAttribute));
这将在 Name 列上创建一个名为 IX_name 的唯一索引。
【讨论】:
【参考方案19】:很抱歉回答晚了,但我觉得和你一起分享它很好
我已在 code project
上发布了相关信息一般来说,这取决于您放在类上的属性来生成唯一索引
【讨论】:
也许提供一些上下文或代码,而不仅仅是一个 URL以上是关于实体框架代码中的唯一约束的主要内容,如果未能解决你的问题,请参考以下文章
通过实体框架更新时如何绕过唯一键约束(使用 dbcontext.SaveChanges())
Coredata Xcode 8中的“在实体的唯一性约束属性中,逗号不是有效属性”