179

Я использую Entity Framework 5.0 Code First;Уникальные ограничения ключа для нескольких столбцов в Entity Framework

public class Entity 
{ 
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    public string EntityId { get; set;} 
    public int FirstColumn { get; set;} 
    public int SecondColumn { get; set;} 
} 

Я хочу сделать сочетание между FirstColumn и SecondColumn как уникальный.

Пример:

Id FirstColumn SecondColumn 
1  1    1  = OK 
2  2    1  = OK 
3  3    3  = OK 
5  3    1  = THIS OK 
4  3    3  = GRRRRR! HERE ERROR 

Есть ли вообще это делать?

ответ

289

С Entity Framework 6.1, теперь вы можете это сделать:

[Index("IX_FirstAndSecond", 1, IsUnique = true)] 
public int FirstColumn { get; set; } 

[Index("IX_FirstAndSecond", 2, IsUnique = true)] 
public int SecondColumn { get; set; } 

Второй параметр в атрибуте, где вы можете указать порядок следования столбцов в индексе.
Дополнительная информация: MSDN

+10

Это верно для аннотаций данных :), если вы хотите, чтобы ответ на использование свободного API см. В ответе Ниаера ниже http: // stackoverflow.com/a/25779348/2362036 – tekiegirl

+8

Не работает для свойств навигации/внешнего ключа – JJS

+7

Но мне нужно, чтобы он работал на внешние ключи! Вы можете мне помочь? –

14

Вам необходимо определить составной ключ.

С аннотациями данных выглядит следующим образом:

public class Entity 
{ 
    public string EntityId { get; set;} 
    [Key] 
    [Column(Order=0)] 
    public int FirstColumn { get; set;} 
    [Key] 
    [Column(Order=1)] 
    public int SecondColumn { get; set;} 
} 

Вы также можете сделать это с MODELBUILDER при переопределении OnModelCreating, указав:

modelBuilder.Entity<Entity>().HasKey(x => new { x.FirstColumn, x.SecondColumn }); 
+1

Но это не ключи, я просто хочу, чтобы они были уникальными. Ключ должен быть идентификатором? Я обновил quesition спасибо за помощь! –

+0

Нехороший способ. – NMathur

+0

Мне нужен был этот ответ. Это соответствует моим потребностям. –

2

Я предполагаю, что вы всегда хотите EntityId быть первичным ключом , поэтому замена его составным ключом не является вариантом (хотя бы потому, что составные клавиши намного сложнее работать с и, потому что не очень разумно иметь первичные ключи, которые также га в смысле бизнес-логики).

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

Кроме того, вы можете (должны) проверить уникальные значения перед сохранением изменений. Лучший способ сделать это с помощью Any() запроса, так как он сводит к минимуму количество передаваемых данных:

if (context.Entities.Any(e => e.FirstColumn == value1 
          && e.SecondColumn == value2)) 
{ 
    // deal with duplicate values here. 
} 

Берегись, что только эта проверка никогда не бывает достаточно. Всегда существует некоторая латентность между проверкой и фактическим фиксацией, поэтому вам всегда потребуется уникальная обработка ограничений и исключений.

+1

Спасибо @GertArnold за ответ, но я не хочу проверять уникальность на бизнес-уровне, это задание базы данных, и это должно быть сделано в базе данных! –

+1

ОК, придерживайтесь уникального индекса. Но в любом случае вам придется иметь дело с нарушениями индексов в бизнес-слое. –

+0

извне, когда я получаю такое исключение, я поймаю и, возможно, сообщит об ошибке и нарушит процесс или выключит приложение. –

52

Я нашел три способа решения проблемы.

Уникальные индексы в EntityFramework Ядро:

Первый подход:

protected override void OnModelCreating(ModelBuilder modelBuilder) 
{ 
    modelBuilder.Entity<Entity>() 
    .HasIndex(p => new {p.FirstColumn , p.SecondColumn}).IsUnique(); 
} 

Второй подход для создания уникальных сдерживающих с EF Ядра с помощью альтернативных ключей.

Примеры

Одна колонка:

modelBuilder.Entity<Blog>().HasAlternateKey(c => c.SecondColumn).HasName("IX_SingeColumn"); 

Несколько столбцов:

modelBuilder.Entity<Entity>().HasAlternateKey(c => new [] {c.FirstColumn, c.SecondColumn}).HasName("IX_MultipleColumns"); 

EF 6 и ниже:


Первый подход:

dbContext.Database.ExecuteSqlCommand(string.Format(
         @"CREATE UNIQUE INDEX LX_{0} ON {0} ({1})", 
           "Entitys", "FirstColumn, SecondColumn")); 

Этот подход является очень быстрым и полезным, но основная проблема заключается в том, что Entity Framework ничего не знает о тех изменениях, не знаю!


Второй подход:
Я нашел его в этой должности, но я не пробовал сам.

CreateIndex("Entitys", new string[2] { "FirstColumn", "SecondColumn" }, 
       true, "IX_Entitys"); 

Проблема этого подхода заключается в следующем: Необходимо DbMigration так что вы будете делать, если вы не имеете его?


Третий подход:
Я думаю, что это лучший один, но это требует некоторого времени, чтобы сделать это. Я просто покажу вам идею, стоящую за этим: В этой ссылке http://code.msdn.microsoft.com/CSASPNETUniqueConstraintInE-d357224a вы можете найти код для уникального ключа аннотирования данных:

[UniqueKey] // Unique Key 
public int FirstColumn { get; set;} 
[UniqueKey] // Unique Key 
public int SecondColumn { get; set;} 

// The problem hier 
1, 1 = OK 
1 ,2 = NO OK 1 IS UNIQUE 

Проблема этого подхода; Как я могу объединить их? У меня есть идея, чтобы расширить эту реализацию Microsoft, например:

[UniqueKey, 1] // Unique Key 
public int FirstColumn { get; set;} 
[UniqueKey ,1] // Unique Key 
public int SecondColumn { get; set;} 

Позже в IDatabaseInitializer, как описано в примере Microsoft вы можете комбинировать ключи в соответствии с заданным числом. Следует отметить одно: если уникальное свойство имеет строку типа, вам необходимо установить MaxLength.

+2

Ты, сэр, спас мне жизнь! – WoIIe

+2

Я использую ** второй подход **, и он работает! – vcRobe

+0

(y) Я нахожу этот ответ лучше. Другое дело, что третий подход не всегда может быть лучшим. (Мне нравится первая на самом деле.) Я лично предпочитаю не иметь каких-либо артефактов EF, перенесенных в классы сущности. – Najeeb

59

Если вы используете код-первых, вы можете реализовать пользовательское расширение HasUniqueIndexAnnotation

using System.ComponentModel.DataAnnotations.Schema; 
using System.Data.Entity.Infrastructure.Annotations; 
using System.Data.Entity.ModelConfiguration.Configuration; 

internal static class TypeConfigurationExtensions 
{ 
    public static PrimitivePropertyConfiguration HasUniqueIndexAnnotation(
     this PrimitivePropertyConfiguration property, 
     string indexName, 
     int columnOrder) 
    { 
     var indexAttribute = new IndexAttribute(indexName, columnOrder) { IsUnique = true }; 
     var indexAnnotation = new IndexAnnotation(indexAttribute); 

     return property.HasColumnAnnotation(IndexAnnotation.AnnotationName, indexAnnotation); 
    } 
} 

Затем используйте его следующим образом:

this.Property(t => t.Email) 
    .HasColumnName("Email") 
    .HasMaxLength(250) 
    .IsRequired() 
    .HasUniqueIndexAnnotation("UQ_User_EmailPerApplication", 0); 

this.Property(t => t.ApplicationId) 
    .HasColumnName("ApplicationId") 
    .HasUniqueIndexAnnotation("UQ_User_EmailPerApplication", 1); 

Это приведет к такой миграции:

public override void Up() 
{ 
    CreateIndex("dbo.User", new[] { "Email", "ApplicationId" }, unique: true, name: "UQ_User_EmailPerApplication"); 
} 

public override void Down() 
{ 
    DropIndex("dbo.User", "UQ_User_EmailPerApplication"); 
} 

И в конечном итоге в базе данных:

CREATE UNIQUE NONCLUSTERED INDEX [UQ_User_EmailPerApplication] ON [dbo].[User] 
(
    [Email] ASC, 
    [ApplicationId] ASC 
) 
+2

Просто то, что мне нужно :) – tekiegirl

+1

Но это индекс не ограничивается! –

+2

В вашем втором кодовом блоке ('this.Property (t => t.Email)'), что такое класс, содержащий класс? (то есть: что такое 'this') – JoeBrockhaus

6

Комплектующие @chuck ответ для использования композитных индексов с внешних ключей.

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

К примеру, у нас есть компании с сотрудниками и только у нас есть уникальное ограничение на (имя, название компании) для любого сотрудника:

class Company 
{ 
    public Guid Id { get; set; } 
} 

class Employee 
{ 
    public Guid Id { get; set; } 
    [Required] 
    public String Name { get; set; } 
    public Company Company { get; set; } 
    [Required] 
    public Guid CompanyId { get; set; } 
} 

теперь отображение класса Employee:

class EmployeeMap : EntityTypeConfiguration<Employee> 
{ 
    public EmployeeMap() 
    { 
     ToTable("Employee"); 

     Property(p => p.Id) 
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 

     Property(p => p.Name) 
      .HasUniqueIndexAnnotation("UK_Employee_Name_Company", 0); 
     Property(p => p.CompanyId) 
      .HasUniqueIndexAnnotation("UK_Employee_Name_Company", 1); 
     HasRequired(p => p.Company) 
      .WithMany() 
      .HasForeignKey(p => p.CompanyId) 
      .WillCascadeOnDelete(false); 
    } 
} 

Обратите внимание, что я также использовал расширение @niaher для уникальной аннотации индекса.

+0

. В этом примере у вас есть как Company, так и CompanyId , Это означает, что вызывающий может изменить один, но не другой, и иметь объект с неправильными данными. – LosManos

+0

@ LosManos Какой абонент вы говорите? Класс представляет данные в базе данных. Изменение значения с помощью запросов обеспечит согласованность. В зависимости от того, что может сделать клиентское приложение, вам может потребоваться выполнить проверки, но это не область действия OP. – Kryptos

Смежные вопросы