Уникальные ограничения ключа для нескольких столбцов в 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.
}
Остерегайтесь, что этой проверки недостаточно. Между проверкой и фактическим фиксацией всегда есть некоторое время ожидания, поэтому вам всегда потребуется уникальная обработка ограничений и исключений.