329 votes

Contraintes de clé unique pour plusieurs colonnes dans Entity Framework

J'utilise Entity Framework 5.0 Code First ;

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

Je veux faire la combinaison entre FirstColumn y SecondColumn comme unique.

Exemple :

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

Existe-t-il un moyen de le faire ?

23voto

Barisa Puter Points 2742

Vous devez définir une clé composite.

Avec les annotations de données, cela ressemble à ceci :

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

Vous pouvez également le faire avec modelBuilder en surchargeant OnModelCreating en spécifiant :

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

10voto

Kryptos Points 855

Compléter la réponse de @chuck pour l'utilisation de indices composites con clés étrangères .

Vous devez définir une propriété qui contiendra la valeur de la clé étrangère. Vous pouvez ensuite utiliser cette propriété dans la définition de l'index.

Par exemple, nous avons une entreprise avec des employés et nous avons une contrainte unique sur (nom, entreprise) pour chaque employé :

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

Voici maintenant le mappage de la classe Employé :

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

Notez que j'ai également utilisé l'extension @niaher pour l'annotation de l'index unique.

5voto

dalios Points 111

Dans la réponse acceptée de @chuck, il y a un commentaire disant que cela ne fonctionnera pas dans le cas de FK.

cela a fonctionné pour moi, dans le cas de 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; }
}

4voto

Gert Arnold Points 27642

Je suppose que vous voulez toujours EntityId est la clé primaire, il n'est donc pas possible de la remplacer par une clé composite (ne serait-ce que parce que les clés composites sont beaucoup plus compliquées à gérer y car il n'est pas très judicieux d'avoir des clés primaires qui ont également une signification dans la logique commerciale).

Le moins que vous puissiez faire est de créer une clé unique pour les deux champs dans la base de données et de vérifier spécifiquement les exceptions de violation de clé unique lors de l'enregistrement des modifications.

En outre, vous pourriez (devriez) vérifier la présence de valeurs uniques avant d'enregistrer les modifications. La meilleure façon de le faire est d'utiliser une fonction Any() car elle minimise la quantité de données transférées :

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

Attention, ce contrôle n'est jamais suffisant. Il y a toujours un certain temps de latence entre la vérification et la validation effective, de sorte que vous aurez toujours besoin de la contrainte unique + la gestion des exceptions.

4voto

RDAxRoadkill Points 393

Je voulais ajouter ma réponse car les solutions proposées ne m'ont pas aidé. Dans mon cas, l'une des colonnes était une référence de clé étrangère.

Ancien modèle :

public class Matrix
{
    public int ID { get; set; }

    public MachineData MachineData { get; set; }

    public MachineVariant MachineVariant { get; set; }
}

Notez que MachineVariant est une énumération et que MachineData est une référence.

J'essaie d'utiliser la solution fournie par @Bassam Alugili :

modelBuilder.Entity<Matrix>()
   .HasIndex(sm => new { sm.MachineData, sm.DoughVariant }).IsUnique(true);

N'a pas fonctionné. J'ai donc ajouté une colonne ID pour la clé étrangère machineData comme suit :

public class Matrix
{
    public int ID { get; set; }

    public MachineData MachineData { get; set; }

    [ForeignKey("MachineData")]
    public int MachineDataID { get; set; }

    public MachineVariant MachineVariant { get; set; }
}

Et j'ai modifié le code du constructeur de modèles comme suit :

modelBuilder.Entity<Matrix>()
   .HasIndex(sm => new { sm.MachineDataID, sm.DoughVariant }).IsUnique(true);

Ce qui a permis d'obtenir la solution souhaitée

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X