131 votes

Contrainte unique dans l'entité Framework Code First

Question

Est-il possible de définir une contrainte unique sur une propriété en utilisant soit la syntaxe courante, soit un attribut? Si non, quelles sont les solutions de contournement?

J'ai une classe d'utilisateurs avec une clé primaire, mais je voudrais m'assurer que l'adresse e-mail est également unique. Est-ce possible sans éditer directement la base de données?

Solution (basée sur la réponse de Matt)

 public class MyContext : DbContext {
    public DbSet<User> Users { get; set; }

    public override int SaveChanges() {
        foreach (var item in ChangeTracker.Entries<IModel>())
            item.Entity.Modified = DateTime.Now;

        return base.SaveChanges();
    }

    public class Initializer : IDatabaseInitializer<MyContext> {
        public void InitializeDatabase(MyContext context) {
            if (context.Database.Exists() && !context.Database.CompatibleWithModel(false))
                context.Database.Delete();

            if (!context.Database.Exists()) {
                context.Database.Create();
                context.Database.ExecuteSqlCommand("alter table Users add constraint UniqueUserEmail unique (Email)");
            }
        }
    }
}
 

63voto

mattmc3 Points 6768

Aussi loin que je peux dire, il n'y a aucun moyen de le faire avec Entity Framework pour le moment. Cependant, ce n'est pas seulement un problème avec les contraintes uniques... vous pouvez créer des index, les contraintes de vérification, et, éventuellement, les déclencheurs et les autres constructions. Voici un modèle simple que vous pouvez utiliser avec votre code de la première configuration, certes ce n'est pas de base de données agnostique:

public class MyRepository : DbContext {
    public DbSet<Whatever> Whatevers { get; set; }

    public class Initializer : IDatabaseInitializer<MyRepository> {
        public void InitializeDatabase(MyRepository context) {
            if (!context.Database.Exists() || !context.Database.ModelMatchesDatabase()) {
                context.Database.DeleteIfExists();
                context.Database.Create();

                context.ObjectContext.ExecuteStoreCommand("CREATE UNIQUE CONSTRAINT...");
                context.ObjectContext.ExecuteStoreCommand("CREATE INDEX...");
                context.ObjectContext.ExecuteStoreCommand("ETC...");
            }
        }
    }
}

Une autre option est que si votre modèle de domaine est la seule méthode d'insertion/mise à jour des données dans votre base de données, vous pouvez mettre en œuvre l'unicité exigence de vous-même et de laisser la base de données. C'est une solution portable et vous oblige à être clair au sujet de vos règles métier dans votre code, mais les feuilles de votre base de données ouverte à des données non valides retour porte.

48voto

Mihkel Müür Points 163

À partir de EF 6.1, il est maintenant possible:

 [Index(IsUnique = true)]
public string EmailAddress { get; set; }
 

Cela vous donnera un index unique au lieu d'une contrainte unique, à proprement parler. Dans la plupart des cas, ils sont identiques .

28voto

lnaie Points 595

Pas vraiment, mais ça peut aider dans certains cas.

Si vous êtes à la recherche pour créer un unique indice composite sur disons 2 colonnes qui va agir comme une contrainte pour votre table, puis à partir de la version 4.3, vous pouvez utiliser les nouvelles migrations mécanisme pour l'atteindre:

Fondamentalement, vous devez insérer un appel de ce genre dans l'un de vos scripts de migration:

CreateIndex("TableName", new string[2] { "Column1", "Column2" }, true, "IX_UniqueColumn1AndColumn2");

Quelque chose comme ça:

namespace Sample.Migrations
{
    using System;
    using System.Data.Entity.Migrations;

    public partial class TableName_SetUniqueCompositeIndex : DbMigration
    {
        public override void Up()
        {
            CreateIndex("TableName", new[] { "Column1", "Column2" }, true, "IX_UniqueColumn1AndColumn2");
        }

        public override void Down()
        {
            DropIndex("TableName", new[] { "Column1", "Column2" });
        }
    }
}

5voto

Kelly Ethridge Points 1005

Je fais un hack complet pour que SQL soit exécuté lors de la création de la base de données. Je crée mon propre DatabaseInitializer et hérite de l'un des initialiseurs fournis.

 public class MyDatabaseInitializer : RecreateDatabaseIfModelChanges<MyDbContext>
{
    protected override void Seed(MyDbContext context)
    {
        base.Seed(context);
        context.Database.Connection.StateChange += new StateChangeEventHandler(Connection_StateChange);
    }

    void Connection_StateChange(object sender, StateChangeEventArgs e)
    {
        DbConnection cnn = sender as DbConnection;

        if (e.CurrentState == ConnectionState.Open)
        {
            // execute SQL to create indexes and such
        }

        cnn.StateChange -= Connection_StateChange;
    }
}
 

C'est le seul endroit que je pourrais trouver dans mes instructions SQL.

Ceci vient de CTP4. Je ne sais pas comment cela fonctionne dans CTP5.

5voto

Rosendo Points 51

Juste essayer de savoir si il y avait une façon de le faire, le seul moyen que j'ai trouvé jusqu'à présent a été mise en œuvre en moi-même, j'ai créé un attribut d'être ajouté à chaque catégorie où vous fournir le nom des champs que vous devez être unique:

    [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple=false,Inherited=true)]
public class UniqueAttribute:System.Attribute
{
    private string[] _atts;
    public string[] KeyFields
    {
        get
        {
            return _atts;
        }
    }
    public UniqueAttribute(string keyFields)
    {
        this._atts = keyFields.Split(new char[]{','}, StringSplitOptions.RemoveEmptyEntries);
    }
}

Puis, dans ma classe, je vais l'ajouter:

[CustomAttributes.Unique("Name")]
public class Item: BasePOCO
{
    public string Name{get;set;}
    [StringLength(250)]
    public string Description { get; set; }
    [Required]
    public String Category { get; set; }
    [Required]
    public string UOM { get; set; }
    [Required]
}

Enfin, je vais ajouter une méthode dans mon référentiel, dans la méthode Add ou lors de l'Enregistrement des Modifications, comme ceci:

private void ValidateDuplicatedKeys(T entity)
{
    var atts = typeof(T).GetCustomAttributes(typeof(UniqueAttribute), true);
    if (atts == null || atts.Count() < 1)
    {
        return;
    }
    foreach (var att in atts)
    {
        UniqueAttribute uniqueAtt = (UniqueAttribute)att;
        var newkeyValues = from pi in entity.GetType().GetProperties()
                            join k in uniqueAtt.KeyFields on pi.Name equals k
                            select new { KeyField = k, Value = pi.GetValue(entity, null).ToString() };
        foreach (var item in _objectSet)
        {
            var keyValues = from pi in item.GetType().GetProperties()
                            join k in uniqueAtt.KeyFields on pi.Name equals k
                            select new { KeyField = k, Value = pi.GetValue(item, null).ToString() };
            var exists = keyValues.SequenceEqual(newkeyValues);
            if (exists)
            {
                throw new System.Exception("Duplicated Entry found");
            }
        }
    }
}

Pas aussi beau que nous devons nous appuyer sur la réflexion, mais jusqu'à présent, est l'approche qui fonctionne pour moi! =D

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