Eindeutige Schlüsseleinschränkungen für mehrere Spalten in Entity Framework

Ich verwende Entity Framework 5.0 Code zuerst;

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

Ich möchte die Kombination zwischen FirstColumn und SecondColumn als einzigartig definieren.

Beispiel:

 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 

Gibt es das überhaupt zu tun?

   

Mit Entity Framework 6.1 können Sie dies jetzt tun:

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

Der zweite Parameter im Attribut gibt an, wo Sie die Reihenfolge der Spalten im Index angeben können.
Weitere Informationen: MSDN

Wenn Sie Code-First verwenden , können Sie eine benutzerdefinierte Erweiterung HasUniqueIndexAnnotation implementieren

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

Dann benutze es so:

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

Was zu dieser Migration führen wird:

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

Und schließlich landen in der database als:

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

Ich habe drei Wege gefunden, das Problem zu lösen.

Eindeutige Indizes in EntityFramework Core:

Erste Ansatz:

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

Der zweite Ansatz zum Erstellen eindeutiger Einschränkungen mit EF Core mithilfe alternativer Schlüssel.

Beispiele

Eine Spalte:

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

Mehrere Spalten:

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

EF 6 und darunter:


Erste Ansatz:

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

Dieser Ansatz ist sehr schnell und nützlich, aber das Hauptproblem ist, dass Entity Framework nichts über diese Änderungen weiß!


Zweiter Ansatz:
Ich habe es in diesem Post gefunden, aber ich habe es nicht selbst versucht.

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

Das Problem dieses Ansatzes ist folgender: Es braucht DbMigration, also was machst du, wenn du es nicht hast?


Dritter Ansatz:
Ich denke, das ist das Beste, aber dafür braucht es etwas Zeit. Ich werde Ihnen nur die Idee dahinter zeigen: In diesem Link http://code.msdn.microsoft.com/CSASPNETUniqueConstraintInE-d357224a finden Sie den Code für eindeutige Schlüsseldaten Annotation:

 [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 

Das Problem für diesen Ansatz; Wie kann ich sie kombinieren? Ich habe eine Idee, diese Microsoft-Implementierung zum Beispiel zu erweitern:

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

Später im IDatabaseInitializer, wie im Microsoft-Beispiel beschrieben, können Sie die Schlüssel entsprechend der angegebenen Ganzzahl kombinieren. Eine Sache muss jedoch beachtet werden: Wenn die eindeutige Eigenschaft vom Typ String ist, müssen Sie MaxLength einstellen.

Sie müssen einen zusammengesetzten Schlüssel definieren.

Mit Datenanmerkungen sieht es so aus:

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

Sie können dies auch mit modelBuilder tun, wenn Sie OnModelCreating überschreiben, indem Sie Folgendes angeben:

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

Vervollständigen Sie @chuck für die Verwendung von zusammengesetzten Indizes mit Fremdschlüsseln .

Sie müssen eine Eigenschaft definieren, die den Wert des Fremdschlüssels enthält. Sie können diese Eigenschaft dann in der Indexdefinition verwenden.

Zum Beispiel haben wir eine Firma mit Angestellten und nur wir haben eine eindeutige Einschränkung für (Name, Firma) für jeden Angestellten:

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

Jetzt das Mapping der Employee-class:

 class EmployeeMap : EntityTypeConfiguration { 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); } } 

Beachten Sie, dass ich auch die @niaher-Erweiterung für eindeutige Indexanmerkungen verwendet habe.

Ich nehme an, dass EntityId immer der Primärschlüssel sein soll, also ist es keine Option, sie durch einen zusammengesetzten Schlüssel zu ersetzen (nur weil zusammengesetzte Schlüssel viel komplizierter sind und weil es nicht sehr sinnvoll ist, Primärschlüssel zu haben) Bedeutung in der Geschäftslogik).

Das Mindeste, was Sie tun sollten, ist, einen eindeutigen Schlüssel für beide Felder in der database zu erstellen und beim Speichern von Änderungen speziell nach eindeutigen Ausnahmen für Schlüsselverletzungen zu suchen.

Zusätzlich könnten (sollten) Sie nach eindeutigen Werten suchen, bevor Sie Änderungen speichern. Der beste Weg, dies zu tun, ist durch eine Any() Abfrage, weil es die Menge der übertragenen Daten minimiert:

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

Beachten Sie, dass diese Überprüfung allein nicht ausreicht. Es gibt immer eine gewisse Latenz zwischen dem Check und dem eigentlichen Commit, so dass Sie immer die einzigartige Constraint + Exception-Behandlung benötigen.