¿Estableciendo restricción única con API fluida?

185

Estoy tratando de construir una entidad EF con Code First y una EntityTypeConfigurationAPI con fluidez. crear claves primarias es fácil, pero no es así con una restricción única. Estaba viendo publicaciones antiguas que sugerían la ejecución de comandos SQL nativos para esto, pero eso parece frustrar el propósito. ¿Es esto posible con EF6?

kob490
fuente

Respuestas:

275

En EF6.2 , puede usar HasIndex()para agregar índices para la migración a través de una API fluida.

https://github.com/aspnet/EntityFramework6/issues/274

Ejemplo

modelBuilder
    .Entity<User>()
    .HasIndex(u => u.Email)
        .IsUnique();

En EF6.1 en adelante, puede usar IndexAnnotation()para agregar índices para la migración en su API fluida.

http://msdn.microsoft.com/en-us/data/jj591617.aspx#PropertyIndex

Debe agregar referencia a:

using System.Data.Entity.Infrastructure.Annotations;

Ejemplo básico

Aquí hay un uso simple, agregando un índice en la User.FirstNamepropiedad

modelBuilder 
    .Entity<User>() 
    .Property(t => t.FirstName) 
    .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute()));

Ejemplo práctico

Aquí hay un ejemplo más realista. Agrega un índice único en múltiples propiedades: User.FirstNamey User.LastName, con un nombre de índice "IX_FirstNameLastName"

modelBuilder 
    .Entity<User>() 
    .Property(t => t.FirstName) 
    .IsRequired()
    .HasMaxLength(60)
    .HasColumnAnnotation(
        IndexAnnotation.AnnotationName, 
        new IndexAnnotation(
            new IndexAttribute("IX_FirstNameLastName", 1) { IsUnique = true }));

modelBuilder 
    .Entity<User>() 
    .Property(t => t.LastName) 
    .IsRequired()
    .HasMaxLength(60)
    .HasColumnAnnotation(
        IndexAnnotation.AnnotationName, 
        new IndexAnnotation(
            new IndexAttribute("IX_FirstNameLastName", 2) { IsUnique = true }));
Yorro
fuente
44
¡Esto es necesario para nombrar la anotación de columna como "Índice"! ¡Escribí otro nombre y no funcionó! Pasé horas antes de intentar cambiarle el nombre al "Índice" original como en tu publicación y entendí que esto es importante. :( Debe haber una constante para que en el marco no codifique la cadena.
Alexander Vasilyev
10
@AlexanderVasilyev La constante se define comoIndexAnnotation.AnnotationName
Nathan
3
@Nathan ¡Gracias! ¡Eso es! El ejemplo en esta publicación debe corregirse usando esta constante.
Alexander Vasilyev
2
Parece que no puedo encontrarlo en EF7 - DNX
Shimmy Weitzhandler
2
Creo que IsUnique debe establecerse en verdadero al crear IndexAttribute en el primer ejemplo. De esta manera: new IndexAttribute() { IsUnique = true }. De lo contrario, crea un índice regular (no exclusivo).
jakubka
134

Como una adición a la respuesta de Yorro, también se puede hacer mediante el uso de atributos.

Muestra para el inttipo de combinación de teclas única:

[Index("IX_UniqueKeyInt", IsUnique = true, Order = 1)]
public int UniqueKeyIntPart1 { get; set; }

[Index("IX_UniqueKeyInt", IsUnique = true, Order = 2)]
public int UniqueKeyIntPart2 { get; set; }

Si el tipo de datos es string, entonces se MaxLengthdebe agregar el atributo:

[Index("IX_UniqueKeyString", IsUnique = true, Order = 1)]
[MaxLength(50)]
public string UniqueKeyStringPart1 { get; set; }

[Index("IX_UniqueKeyString", IsUnique = true, Order = 2)]
[MaxLength(50)]
public string UniqueKeyStringPart2 { get; set; }

Si existe un problema de separación de modelo de dominio / almacenamiento, el uso de Metadatatypeatributo / clase puede ser una opción: https://msdn.microsoft.com/en-us/library/ff664465%28v=pandp.50%29.aspx?f= 255 & MSPPError = -2147217396


Un ejemplo rápido de la aplicación de consola:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;

namespace EFIndexTest
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new AppDbContext())
            {
                var newUser = new User { UniqueKeyIntPart1 = 1, UniqueKeyIntPart2 = 1, UniqueKeyStringPart1 = "A", UniqueKeyStringPart2 = "A" };
                context.UserSet.Add(newUser);
                context.SaveChanges();
            }
        }
    }

    [MetadataType(typeof(UserMetadata))]
    public class User
    {
        public int Id { get; set; }
        public int UniqueKeyIntPart1 { get; set; }
        public int UniqueKeyIntPart2 { get; set; }
        public string UniqueKeyStringPart1 { get; set; }
        public string UniqueKeyStringPart2 { get; set; }
    }

    public class UserMetadata
    {
        [Index("IX_UniqueKeyInt", IsUnique = true, Order = 1)]
        public int UniqueKeyIntPart1 { get; set; }

        [Index("IX_UniqueKeyInt", IsUnique = true, Order = 2)]
        public int UniqueKeyIntPart2 { get; set; }

        [Index("IX_UniqueKeyString", IsUnique = true, Order = 1)]
        [MaxLength(50)]
        public string UniqueKeyStringPart1 { get; set; }

        [Index("IX_UniqueKeyString", IsUnique = true, Order = 2)]
        [MaxLength(50)]
        public string UniqueKeyStringPart2 { get; set; }
    }

    public class AppDbContext : DbContext
    {
        public virtual DbSet<User> UserSet { get; set; }
    }
}
coni2k
fuente
45
No, si desea mantener su modelo de dominio completamente separado de las preocupaciones de almacenamiento.
Rickard Liljeberg
44
También debe asegurarse de tener una referencia a EntityFramework
Michael Tranchida
2
Sea amable si el atributo Index se separó de Entity Framework para poder incluirlo en mi proyecto de Modelos. Entiendo que es un problema de almacenamiento, pero la razón principal por la que lo uso es para imponer restricciones únicas en cosas como UserNames y Role Names.
Sam
2
Parece que no puedo encontrarlo en EF7 - DNX
Shimmy Weitzhandler
44
Esto solo funciona si también restringe la longitud de la cadena, ya que SQL no permite que nvarchar (max) se use como clave.
JMK
17

Aquí hay un método de extensión para establecer índices únicos con mayor fluidez:

public static class MappingExtensions
{
    public static PrimitivePropertyConfiguration IsUnique(this PrimitivePropertyConfiguration configuration)
    {
        return configuration.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute { IsUnique = true }));
    }
}

Uso:

modelBuilder 
    .Entity<Person>() 
    .Property(t => t.Name)
    .IsUnique();

Generará migración como:

public partial class Add_unique_index : DbMigration
{
    public override void Up()
    {
        CreateIndex("dbo.Person", "Name", unique: true);
    }

    public override void Down()
    {
        DropIndex("dbo.Person", new[] { "Name" });
    }
}

Src: Creación de un índice único con API fluida de Entity Framework 6.1

Bartho Bernsmann
fuente
16

La respuesta de @ coni2k es correcta, sin embargo, debe agregar un [StringLength]atributo para que funcione; de ​​lo contrario, obtendrá una excepción de clave no válida (Ejemplo a continuación).

[StringLength(65)]
[Index("IX_FirstNameLastName", 1, IsUnique = true)]
public string FirstName { get; set; }

[StringLength(65)]
[Index("IX_FirstNameLastName", 2, IsUnique = true)]
public string LastName { get; set; }
Arijoon
fuente
0
modelBuilder.Property(x => x.FirstName).IsUnicode().IsRequired().HasMaxLength(50);
Rousonur Jaman
fuente