Уникальное ограничение в коде Entity Framework First - PullRequest
125 голосов
/ 10 декабря 2010

Вопрос

Можно ли определить уникальное ограничение для свойства, используя либо свободный синтаксис, либо атрибут?Если нет, каковы обходные пути?

У меня есть класс пользователя с первичным ключом, но я хотел бы убедиться, что адрес электронной почты также является уникальным.Возможно ли это без непосредственного редактирования базы данных?

Решение (на основе ответа Мэтта)

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 ]

1 голос
/ 01 апреля 2014

С подходом 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 +.

1 голос
/ 21 октября 2013

Если вы используете EF5 и у вас все еще есть этот вопрос, решение, приведенное ниже, решило его для меня.

Я использую код в первую очередь, поэтому ставлю:

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

в скрипте миграции хорошо поработали. Также допускаются значения NULL!

0 голосов
/ 29 апреля 2014

После прочтения этого вопроса у меня возник собственный вопрос, когда я пытался реализовать атрибут для обозначения свойств в качестве уникальных ключей, таких как , * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *. ответов предполагают: Сопоставить свойства кода Entity Framework со столбцами базы данных (от CSpace до SSpace)

Я наконец пришел к этому ответу, который может сопоставить скалярные и навигационные свойства со столбцами базы данных и создать уникальный индекс в определенной последовательности, указанной в атрибуте. В этом коде предполагается, что вы реализовали UniqueAttribute со свойством Sequence и применили его к свойствам класса сущности 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
0 голосов
/ 24 февраля 2014

Согласно http://blogs.msdn.com/b/adonet/archive/2014/02/11/ef-6-1-0-beta-1-available.aspx, EF 6.1 будет иметь IndexAttribute, чтобы помочь нам.

0 голосов
/ 29 октября 2015

Для тех, кто использует конфигурации с первым кодом, вы также можете использовать объект IndexAttribute как ColumnAnnotation и установить для его свойства IsUnique значение true.

Например:

var indexAttribute = new IndexAttribute("IX_name", 1) {IsUnique = true};

Property(i => i.Name).HasColumnAnnotation("Index",new IndexAnnotation(indexAttribute));

Это создаст уникальный индекс с именем IX_name в столбце Имя.

0 голосов
/ 04 октября 2012

Я столкнулся с этой проблемой сегодня и наконец смог ее решить. Я не знаю, правильный ли это подход, но, по крайней мере, я могу продолжать:

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);
        }
    }
}
0 голосов
/ 04 октября 2011

Используйте уникальный валидатор свойства.

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 голосов
/ 20 октября 2016

Прошу прощения за поздний ответ, но мне было приятно поболтать с вами

Я написал об этом на код проекта

В общем, это зависит от атрибутов, которые вы наделили на классы для генерации уникальных индексов

0 голосов
/ 11 мая 2013

Поскольку встроенной аннотации нет, я нашел обходной путь.Пожалуйста, обратитесь к этой ссылке для получения дополнительной информации https://stackoverflow.com/a/16496291/1873113

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...