Унікальні ключові обмеження для кількох стовпців в Entity Framework


243

Я спочатку використовую код Entity Framework 5.0;

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

Чи все-таки це потрібно зробити?

Відповіді:


369

З 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


12
Це вірно для анотацій даних :), якщо ви хочете , щоб відповідь на використання текучого API см відповідь Niaher нижче діючої stackoverflow.com/a/25779348/2362036
tekiegirl

8
Але мені це потрібно для роботи над іноземними ключами! Можеш допомогти мені?
feedc0de

2
@ 0xFEEDC0DE див. Мою відповідь нижче, яка стосується використання сторонніх ключів в індексах.
Криптос

1
Чи можете ви розмістити, як використовувати цей індекс із linq to sql?
Bluebaron

4
@JJS - я змусив його працювати там, де однією з властивостей був іноземний ключ. Яким випадком є ​​ваш ключ варчар чи нварчар? Існує обмеження тривалості використання якого можна використовувати як унікальний ключ .. stackoverflow.com/questions/2863993/…
Дейв Лоуренс

129

Я знайшов три способи вирішити проблему.

Унікальні індекси в EntityFramework Core:

Перший підхід:

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

Другий підхід для створення унікальних обмежень з EF Core за допомогою альтернативних клавіш.

Приклади

Один стовпець:

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.


1
(у) я вважаю цю відповідь кращою. Інша справа, третій підхід може не обов’язково бути найкращим. (Насправді мені подобається перший.) Особисто я вважаю за краще не мати жодних артефактів EF, перенесених у мої класи.
Najeeb

1
Можливо, другий підхід повинен бути: CREATE UNIQUE INDEX ix_{1}_{2} ON {0} ({1}, {2})? (див. BOL )
АК

2
Дурне запитання: Чому так ви починаєте все своє ім'я на "IX_"?
Бастьєн Vandamme

1
@BastienVandamme це гарне питання. індекс автоматичного генерування по EF починається з IX_. Здається, це умовне значення в індексі EF за замовчуванням, ім'я індексу буде IX_ {ім'я властивості}.
Bassam Alugili

1
Так, так і має бути. Дякуємо за реалізацію API Fluent. Про це є серйозний брак документації
JSON

75

Якщо ви використовуєте Code-First , ви можете реалізувати спеціальне розширення 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
)

3
Але це індекс не обмеження!
Роман Покровський

3
Що у вашому другому блоці коду ( this.Property(t => t.Email)) що це містить клас? (тобто: що є this)
JoeBrockhaus

2
нвм. EntityTypeConfiguration<T>
JoeBrockhaus

5
@RomanPokrovskij: Різниця між унікальним індексом та унікальним обмеженням виглядає як питання про те, як зберігаються записи про нього у SQL Server. Докладні відомості див. У technet.microsoft.com/en-us/library/aa224827%28v=sql.80%29.aspx .
Mass Dot Net

1
@niaher Я ціную ваш приємний метод продовження
Мохсен Афшин

18

Потрібно визначити складений ключ.

З примітками про дані це виглядає приблизно так:

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

2
Але це не ключі, я просто хочу їх, оскільки унікальним ключем повинен бути Id? Я оновив питання, дякую за допомогу!
Bassam Alugili

16

Відповідь від niaher про те, що для використання вільного API вам потрібне спеціальне розширення під час написання. Тепер ви можете (ядро EF 2.1) використовувати вільний API таким чином:

modelBuilder.Entity<ClassName>()
            .HasIndex(a => new { a.Column1, a.Column2}).IsUnique();

9

Завершення @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; }
}

Тепер відображення карти класу Співробітник:

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 для унікальної анотації індексу.


1
У цьому прикладі ви маєте і Company, і CompanyId. Це означає, що абонент може змінити одне, але не інше, і мати об'єкт з неправильними даними.
ЛосМанос

1
@LosManos Про кого телефонуєш? Клас представляє дані в базі даних. Зміна значення за допомогою запитів забезпечить послідовність. Залежно від того, що може зробити клієнтська програма, можливо, вам знадобиться здійснити перевірки, але це не сфера дії ОП.
Криптос

4

У прийнятій відповіді від @chuck є коментар, в якому говориться, що це не спрацює у випадку з FK.

це працювало для мене, випадок EF6 .Net4.7.2

public class OnCallDay
{
     public int Id { get; set; }
    //[Key]
    [Index("IX_OnCallDateEmployee", 1, IsUnique = true)]
    public DateTime Date { get; set; }
    [ForeignKey("Employee")]
    [Index("IX_OnCallDateEmployee", 2, IsUnique = true)]
    public string EmployeeId { get; set; }
    public virtual ApplicationUser Employee{ get; set; }
}

Довгий час. скажімо, це працювало раніше! дякую за оновлення, будь ласка, додайте коментар до відповіді @chuck. Я думаю, що Чак до цього часу не використовує SO.
Bassam Alugili

Чи потрібний властивість EmployeeeID тут атрибутом, щоб обмежити його довжину, щоб він міг індексуватися? Інакше створено за допомогою VARCHAR (MAX), який не може мати індекс? Додати атрибут [StringLength (255)] для EmployeeID
Лорд Дарт Вейдер

EmployeeeID - це GUID. Багато навчальних посібників пропонують
навести

3

Я припускаю, що ви завжди хочете EntityIdбути первинним ключем, тому замінити його на складений ключ не є варіантом (хоча б тому, що з складовими ключами набагато складніше працювати і тому, що не дуже розумно мати первинні ключі, які також мають значення у логіці бізнесу).

Найменше, що вам слід зробити, - це створити унікальний ключ в обох полях бази даних та спеціально перевірити наявність унікальних винятків порушення ключа при збереженні змін.

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

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

Слідкуйте за тим, щоб цього перевірки ніколи не вистачало. Завжди існує деяка затримка між чеком і фактичною комісією, тому вам завжди знадобиться унікальне обмеження + обробка виключень.


3
Дякую @GertArnold за відповідь, але я не хочу перевіряти унікальність на рівні бізнесу. Це робота з базою даних, і це потрібно робити в базі даних!
Bassam Alugili

2
Добре, тоді дотримуйтесь унікального індексу. Але вам доведеться стикатися з порушеннями індексу на рівні бізнесу.
Герт Арнольд

1
ззовні, коли я отримаю подібний виняток, я підхоплюю і, ніж можливо, повідомляю про помилку та порушую процес або закриваю програму.
Bassam Alugili

3
Так, ... чи потрібно мені на це реагувати? Пам’ятайте, що я нічого не знаю про вашу заявку, я не можу сказати вам, який найкращий спосіб - це поводження з цими винятками, тільки що ви маєте справу з ними.
Герт Арнольд

2
Будьте обережні до унікальних обмежень БД з EF. Якщо ви зробите це, а потім у вас з'явиться оновлення, яке перевертає значення одного з стовпців, що є частиною унікального ключа, сутність frameowkr не вдасться зберегти, якщо ви не додасте цілий рівень транзакції. Наприклад: об’єкт «Сторінка» має дочірню колекцію елементів. Кожен елемент має SortOrder. Ви хочете, щоб комбінація PageID і SortOrder була унікальною. На передньому кінці, користувач перегортає порядок елементів із сортуванням 1 та 2. Entity Framework не зможе зберегти b / c, який намагається оновити сортировки по одному.
EGP

1

Нещодавно додали складений ключ з унікальністю 2 стовпців, використовуючи підхід, який рекомендував "патрон", дякую @chuck. Тільки цей наближений до мене виглядав чистішим:

public int groupId {get; set;}

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

[Index("IX_ClientGrouping", 2, IsUnique = true)]
public int GroupName { get; set; }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.