web-dev-qa-db-fra.com

Contraintes de clé uniques 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 souhaite que la combinaison entre FirstColumn et SecondColumn soit 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

Y'a-t'il un quelconque moyen d'y arriver?

213
Bassam Alugili

Avec Entity Framework 6.1, vous pouvez maintenant faire ceci:

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

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

Le deuxième paramètre de l'attribut est l'endroit où vous pouvez spécifier l'ordre des colonnes dans l'index.
Plus d'informations: MSDN

339
chuck

J'ai trouvé trois façons de résoudre le problème.

Index uniques dans EntityFramework Core:

Première approche:

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

La deuxième approche consiste à créer des contraintes uniques avec EF Core à l'aide de clés alternatives.

Exemples

Une colonne:

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

Plusieurs colonnes:

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

EF 6 et inférieur:


Première approche:

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

Cette approche est très rapide et utile, mais le problème principal est qu'Entity Framework ne sait rien de ces changements!


Deuxième approche:
Je l’ai trouvé dans cet article, mais je n’ai pas essayé seul.

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

Le problème de cette approche est le suivant: DbMigration est nécessaire. Que faites-vous si vous ne l'avez pas?


Troisième approche:
Je pense que c'est le meilleur, mais il faut un peu de temps pour le faire. Je vais simplement vous montrer l’idée sous-jacente: Dans ce lien http://code.msdn.Microsoft.com/CSASPNETUniqueConstraintInE-d357224a vous pouvez trouver le code pour une annotation de données de clé unique:

[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

Le problème pour cette approche; Comment puis-je les combiner? J'ai une idée pour étendre cette implémentation Microsoft par exemple:

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

Plus tard, dans IDatabaseInitializer, comme décrit dans l'exemple Microsoft, vous pouvez combiner les clés en fonction de l'entier donné. Il convient toutefois de noter une chose: si la propriété unique est de type chaîne, vous devez définir la longueur maximale.

102
Bassam Alugili

Si vous utilisez Code-First, vous pouvez implémenter une extension personnalisée 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);
    }
}

Alors utilisez-le comme suit:

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

Ce qui entraînera cette migration:

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

Et finissent par se retrouver dans la base de données en tant que:

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

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 lors de la substitution de OnModelCreating en spécifiant:

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

Remplir @chuck answer pour utiliser indices composites avec 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 seulement nous avons une contrainte unique sur (nom, entreprise) pour tout 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; }
}

Maintenant, le mappage de la classe 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);
    }
}

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

8
Kryptos

La réponse de niaher indiquant que pour utiliser l'API fluide, vous avez besoin d'une extension personnalisée peut avoir été correcte au moment de la rédaction. Vous pouvez maintenant (EF core 2.1) utiliser l’API fluide comme suit:

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

Je suppose que vous voulez toujours que EntityId soit 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 à utiliser et car n’est pas très judicieux d’avoir des clés primaires qui ont également une signification dans la logique métier).

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

De plus, vous pouvez (devriez) vérifier les valeurs uniques avant d'enregistrer les modifications. La meilleure façon de le faire est d'utiliser une requête 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 chèque seul n'est jamais suffisant. Il y a toujours une certaine latence entre la vérification et la validation, vous aurez donc toujours besoin de la gestion unique de la contrainte et des exceptions.

2
Gert Arnold