2011-04-18 21 views
56

Tengo un modelo siguiente en mi proyectoclave única con el código de EF primera

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

y yo estoy tratando de hacer Title clave como único, busqué en Google para la solución, pero no pudo encontrar ninguna. ¿Puede alguien sugerirme cómo hacerlo, por favor?

Respuesta

100

Desafortunadamente, no se puede definir primero como una clave única en el código porque EF no admite claves únicas en absoluto (se espera que esté planificado para la siguiente versión principal). Lo que puede hacer es crear intializer base de datos personalizada y añadir manualmente índice único llamando al comando SQL:

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

Y debe establecer este inicializador en el arranque de su aplicación.

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

Editar

ahora (EF 6.1 en adelante) se puede fácilmente tener restricciones únicas,

[Index("TitleIndex", IsUnique = true)] 
public string Title { get; set; } 
+0

Trabajo con MVC 3 y EF 4 y el código no reconoce ExecuteSqlCommand en context.Database.ExecuteSqlCommand ("CREATE UNIQUE INDEX IX_Category_Title ON Categories (Title)"); ¿se trata de una versión u otra? – Saeid

+1

@Saeid: esto es para DbContext API (EFv4.1). No hay inicializador de base de datos en EFv4. ObjectContext API ofrece sus propios métodos para ejecutar SQL directamente - 'ExecuteStoreCommand'. –

+1

También es una gran manera de agregar restricciones predeterminadas (por ejemplo, GETDATE(), etc.) –

2

Aquí está la versión VB.Net - tenga en cuenta la aplicación de los genéricos que es una poco diferente, en el nivel de clase.

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 
+0

oh vamos - ¿qué hay de malo en agregar una versión concisa de VB, para usuarios de vb con el mismo problema? ¿no es este el objetivo de SO, proporcionar un recurso no solo para el póster original? Además, como se señaló, la implementación es algo diferente. – GilShalit

21

En primer lugar crear la clase de atributo personalizado:

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

A continuación, añadir a sus clases:

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

continuación, agregar un inicializador en su 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 + ")"); 
         } 
        } 
       } 
      } 
     } 
    } 
} 

Y para último agregar el Initializ er en Application_Start dentro de Global.asax.cs

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

Eso es todo. basado en el código vb en https://stackoverflow.com/a/7426773

+1

Par de correcciones. 1. tableName debe incluirse entre corchetes durante ExecuteSqlCommand 2. si está utilizando nombres no pluralizados, utilice else {tableName = t.Name} – James

0

Creo esta clase (que se ha mejorado a partir de otra respuesta de Stackoverflow - Execute a large SQL script (with GO commands)), lo que me permite incluir las secuencias de comandos SQL en un directorio y ejecutarlas todas cada vez que sean necesarias (Semilla, o Migración). No voy a dejarlo abierto después de implementarlo en producción, pero durante el desarrollo facilita la aplicación de scripts cada vez que se recrea el DB.

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

} }

Esto es lo que parece la solución VS como:

Cuestiones relacionadas