web-dev-qa-db-fra.com

Clé unique avec le code EF en premier

J'ai un modèle suivant dans mon projet

public class Category
{   
    public Guid ID { get; set; }
    [Required(ErrorMessage = "Title cannot be empty")]
    public string Title { get; set; }
}

et j'essaie de créer Title comme clé unique, j'ai recherché la solution sur Google, mais je n'en ai pas trouvé. Quelqu'un peut-il me suggérer comment le faire, s'il vous plaît?

62

Malheureusement, vous ne pouvez pas le définir d'abord comme clé unique dans le code car EF ne prend pas du tout en charge les clés uniques (il est à espérer qu'il est prévu pour la prochaine version majeure). Ce que vous pouvez faire est de créer un intialiseur de base de données personnalisé et d'ajouter manuellement un index unique en appelant la commande SQL:

public class MyInitializer : CreateDatabaseIfNotExists<MyContext>
{
  protected override void Seed(MyContext context)
  {
    context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX IX_Category_Title ON Categories (Title)");
  }
}

Et vous devez définir cet initialiseur dans le bootstrap de votre application.

Database.SetInitializer<MyContext>(new MyInitializer());

Modifier

Maintenant (EF 6.1 et suivants), vous pouvez facilement avoir des contraintes uniques,

[Index("TitleIndex", IsUnique = true)]
 public string Title { get; set; }
108
Ladislav Mrnka

Créez d'abord la classe d'attributs personnalisés:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UniqueAttribute : ValidationAttribute
{
   public override Boolean IsValid(Object value)
    {
        // constraint implemented on database
        return true;
    }
}

Ajoutez ensuite à vos cours:

public class Email
{
    [Key]
    public int EmailID { get; set; }

    public int PersonId { get; set; }

    [Unique]
    [Required]
    [MaxLength(100)]
    public string EmailAddress { get; set; }
    public virtual bool IsDefault { get; set; }
    public virtual Boolean IsApprovedForLogin { get; set; }
    public virtual String ConfirmationToken { get; set; }

    [ForeignKey("PersonId")]
    public virtual Person Person { get; set; }
}

Ajoutez ensuite un initialiseur sur votre DbContext:

public class Initializer : IDatabaseInitializer<myEntities>
{
    public void InitializeDatabase(myEntities context)
    {
        if (System.Diagnostics.Debugger.IsAttached && context.Database.Exists() && !context.Database.CompatibleWithModel(false))
        {
            context.Database.Delete();
        }

        if (!context.Database.Exists())
        {
            context.Database.Create();

            var contextObject = context as System.Object;
            var contextType = contextObject.GetType();
            var properties = contextType.GetProperties();
            System.Type t = null;
            string tableName = null;
            string fieldName = null;
            foreach (var pi in properties)
            {
                if (pi.PropertyType.IsGenericType && pi.PropertyType.Name.Contains("DbSet"))
                {
                    t = pi.PropertyType.GetGenericArguments()[0];

                    var mytableName = t.GetCustomAttributes(typeof(TableAttribute), true);
                    if (mytableName.Length > 0)
                    {
                        TableAttribute mytable = mytableName[0] as TableAttribute;
                        tableName = mytable.Name;
                    }
                    else
                    {
                        tableName = pi.Name;
                    }

                    foreach (var piEntity in t.GetProperties())
                    {
                        if (piEntity.GetCustomAttributes(typeof(UniqueAttribute), true).Length > 0)
                        {
                            fieldName = piEntity.Name;
                            context.Database.ExecuteSqlCommand("ALTER TABLE " + tableName + " ADD CONSTRAINT con_Unique_" + tableName + "_" + fieldName + " UNIQUE (" + fieldName + ")");
                        }
                    }
                }
            }
        }
    }
}

Et pour la dernière fois, ajoutez l'initialiseur à Application_Start dans Global.asax.cs

System.Data.Entity.Database.SetInitializer<MyApp.Models.DomainModels.myEntities>(new MyApp.Models.DomainModels.myEntities.Initializer());

C'est ça. basé sur le code vb à https://stackoverflow.com/a/742677

22
Joao Leme

Voici la version VB.Net - notez l'implémentation de génériques un peu différents, au niveau de la classe.

Public Class MyInitializer(Of T As DbContext)
    Inherits CreateDatabaseIfNotExists(Of T)
    Protected Overrides Sub Seed(context As T)
        context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX IX_Category_Title ON Categories (Title)")
    End Sub
End Class
2
GilShalit

J'ai trouvé cette solution qui, bien que ne créant pas de clé unique au niveau SQL, utilise la validation DataAnnotations, vérifiez-la:

http://blogs.Microsoft.co.il/blogs/shimmy/archive/2012/01/23/validationattribute-that-validates-a-unique-field-against-its-fellow-rows-in- the-database.aspx

0
Shimmy

Je crée cette classe (qui a été améliorée à partir d'une autre réponse Stackoverflow - Exécuter un grand script SQL (avec les commandes GO) ), ce qui me permet de déposer les scripts SQL dans un répertoire et de les faire exécuter tous chaque fois qu'ils sont nécessaires (semences ou migration). Je ne vais pas laisser cela ouvert après mon déploiement en production, mais pendant le développement, il est facile d'appliquer des scripts à chaque recréation de la base de données.

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//dll Microsoft.SqlServer.Smo
//dll Microsoft.SqlServer.Management.Sdk.Sfc
//dll Microsoft.SqlServer.ConnectionInfo
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using Monitor.Common;

namespace MonitorDB.DataLayer.Migrations
{
  public class ExecuteSQLScripts :Monitor.Common.ExceptionHandling
  {
    public ExecuteSQLScripts()
    {
}

public bool ExecuteScriptsInDirectory(DBContext.SolArcMsgMonitorContext context, string scriptDirectory)
{
  bool Result = false;
  try
  {
    SqlConnection connection = new SqlConnection(context.Database.Connection.ConnectionString);
    Server server = new Server(new ServerConnection(connection));

    DirectoryInfo di = new DirectoryInfo(scriptDirectory);
    FileInfo[] rgFiles = di.GetFiles("*.sql");
    foreach (FileInfo fi in rgFiles)
    {

      FileInfo fileInfo = new FileInfo(fi.FullName);
      string script = fileInfo.OpenText().ReadToEnd();

      server.ConnectionContext.ExecuteNonQuery(script);
    }
    Result = true;
  }
  catch (Exception ex)
  {
    CatchException("ExecuteScriptsInDirectory", ex);
  }
  return Result;
}

}}

Voici à quoi ressemble la solution VS:

0
codeputer