Уникальные ограничения ключа для нескольких столбцов в Entity Framework

Я использую First Entity Framework 5.0 Code;

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

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

Ответ 1

С 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

Ответ 2

Если вы используете 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

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

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

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

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.

Ответ 4

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

С аннотациями данных это выглядит так:

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

Ответ 5

Завершение ответа @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 для уникальной аннотации индекса.

Ответ 6

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

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

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

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

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