首页 文章

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

提问于
浏览
125

Question

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

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

Solution (based on Matt's answer)

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

19 回答

  • 43

    据我所知,只有一个问题是独特的约束......你可能想要创建索引,检查约束,以及可能的触发器和其他构造 . 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...");
                }
            }
        }
    }
    

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

  • 0

    从EF 6.1开始,现在可以:

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

    严格来说,这将为您提供唯一的索引,而不是唯一约束 . 对于大多数实际用途they are the same .

  • 0

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

    如果您要创建一个唯一的复合索引,我们说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" });
            }
        }
    }
    
  • 0

    在创建数据库时,我做了一个完整的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;
        }
    }
    

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

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

  • 3

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

    [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

  • 5

    同样在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/

  • 28

    使用EF5 Code First Migrations在visual basic中的一种简单方法

    公共类样本

    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
    
  • 1

    类似于Tobias Schittkowski的回答,但是C#并且有能力在constrtaints中有多个字段 .

    要使用此功能,只需在您希望独特的任何字段上放置[唯一]即可 . 对于字符串,您必须执行类似的操作(请注意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&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))));
        }
    }
    
  • 5

    如果重写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; }
        }
    }
    
  • 0

    我通过反思解决了这个问题(抱歉,伙计,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(在我的版本中,我只在调试模式下重新创建数据库更改...) . 在此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
    

    也许这有助于......

  • 5

    如果你正在使用EF5并且仍然有这个问题,下面的解决方案为我解决了它 .

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

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

    在迁移脚本中完成了这项工作 . 它还允许NULL值!

  • 3

    使用EF代码第一种方法,可以使用以下技术实现基于属性的唯一约束支持 .

    创建标记属性

    [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一起使用 .

  • 0

    我今天遇到了这个问题,最后我能够解决它 . 我不知道是否是正确的方法,但至少我可以坚持下去:

    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);
            }
        }
    }
    
  • 1

    使用唯一的属性验证器 .

    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();
    }
    
  • 0

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

  • 1

    根据http://blogs.msdn.com/b/adonet/archive/2014/02/11/ef-6-1-0-beta-1-available.aspx,EF 6.1将有一个IndexAttribute来帮助我们 .

  • 1

    在阅读了这个问题后,我在尝试实现属性指定属性的过程中有了自己的问题,如Mihkel Müür'sTobias Schittkowski'smheyman's答案提示:Map Entity Framework code properties to database columns (CSpace to SSpace)

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

    Note: 此代码依赖于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
    
  • 0

    对于使用代码优先配置的用户,您还可以将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的唯一索引 .

  • 61

    对不起,迟到的答案,但我觉得很高兴与你共事

    I have posted about this at code project

    通常,它取决于您在类上生成唯一索引的属性

相关问题