EF Core Mapping EntityTypeConfiguration

129

En EF6 usualmente podemos usar esta forma para configurar la Entidad.

public class AccountMap : EntityTypeConfiguration<Account>
{
    public AccountMap()
    {
        ToTable("Account");
        HasKey(a => a.Id);

        Property(a => a.Username).HasMaxLength(50);
        Property(a => a.Email).HasMaxLength(255);
        Property(a => a.Name).HasMaxLength(255);
    }
}

Cómo podemos hacerlo en EF Core, desde cuando la clase I Hereda EntityTypeConfiguration que no puede encontrar la clase.

Descargo el código fuente bruto de EF Core desde GitHub, no puedo encontrarlo. ¿Alguien puede ayudar en esto?

Germán
fuente
8
¿Por qué no aceptar esa respuesta?
Den
ya que en beta5 ahora, cuando ponemos maxLength (50). en el db genera nvarchar (max)
Herman
66
Para cualquier persona interesada en esto, ahora hay una IEntityTypeConfiguration<T>con un void Configure()método que se puede implementar. Detalles aquí: github.com/aspnet/EntityFramework/pull/6989
Galilyou

Respuestas:

183

Desde EF Core 2.0 existe IEntityTypeConfiguration<TEntity>. Puedes usarlo así:

class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
  public void Configure(EntityTypeBuilder<Customer> builder)
  {
     builder.HasKey(c => c.AlternateKey);
     builder.Property(c => c.Name).HasMaxLength(200);
   }
}

...
// OnModelCreating
builder.ApplyConfiguration(new CustomerConfiguration());

Puede encontrar más sobre esta y otras nuevas características introducidas en 2.0 aquí .

Krzysztof Branicki
fuente
8
Esta es la mejor respuesta para EF Core 2.0. ¡Gracias!
Collin M. Barrett
2
Esto es excelente. Estaba buscando una manera de separar las definiciones fluidas de API. Gracias
Blaze
También vea esta respuesta para "ToTable" y "HasColumnName", etc ::: stackoverflow.com/questions/43200184/…
granadaCoder
si tiene una configuración personalizada de hombre, solo builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);tiene que aplicar todas las
confirmaciones
52

Puede lograr esto a través de algunos tipos adicionales simples:

internal static class ModelBuilderExtensions
{
   public static void AddConfiguration<TEntity>(
     this ModelBuilder modelBuilder, 
     DbEntityConfiguration<TEntity> entityConfiguration) where TEntity : class
   {     
       modelBuilder.Entity<TEntity>(entityConfiguration.Configure);
   }
}

internal abstract class DbEntityConfiguration<TEntity> where TEntity : class
{     
    public abstract void Configure(EntityTypeBuilder<TEntity> entity);
}

Uso:

internal class UserConfiguration : DbEntityConfiguration<UserDto>
{
    public override void Configure(EntityTypeBuilder<UserDto> entity)
    {
        entity.ToTable("User");
        entity.HasKey(c => c.Id);
        entity.Property(c => c.Username).HasMaxLength(255).IsRequired();
        // etc.
    }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.AddConfiguration(new UserConfiguration());
}
devdigital
fuente
1
¿Dónde está ForSqlServerToTable()?
im1dermike
1
Esto es ahora ToTable, ver docs.microsoft.com/en-us/ef/core/modeling/relational/tables
devdigital
1
¿Cómo usar HasColumnType con esto? . Por ej. entity.Property(c => c.JoinDate).HasColumnType("date");
Biju Soman
OnModelCreatingha sido actualizado para requerir a DbModelBuilder. La forma de agregar configuraciones a esto es ahoramodelBuilder.Configurations.Add(new UserConfiguration());
Izzy
2
@Izzy: DbModelBuilder es Entity Framework 6.0, ModelBuilder es EF Core. Son conjuntos diferentes y, en este caso, la pregunta era específica de EF Core.
Jason
29

En EF7, anula OnModelCreating en la clase DbContext que está implementando.

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Account>()
            .ForRelational(builder => builder.Table("Account"))
            .Property(value => value.Username).MaxLength(50)
            .Property(value => value.Email).MaxLength(255)
            .Property(value => value.Name).MaxLength(255);
    }
Avi Cherry
fuente
23
Entonces, si tengo 20 configuraciones de tipo de entidad, ¿las pongo en un método enorme?
Den
66
Por defecto, parece que sí. Puede crear sus propias clases de FooMapper / FooModelBuilder que extiendan una clase base y tengan un método para pasar un EntityBuilder <Foo> escrito. ¡Incluso podría usar la nueva inyección de dependencia y la interfaz de IConfiguration para que las descubran / llamen automáticamente, si quisiera ser elegante!
Avi Cherry
1
De nada. ¡Votar una respuesta (y alentar al interlocutor a aceptarla) es aún mejor!
Avi Cherry
Usualmente hago esto :)
Den
44
Prueba las nuevas herramientas de inyección de dependencias? Haga una IEntityMapperStrategyinterfaz con una void MapEntity(ModelBuilder, Type)firma y bool IsFor(Type). Implemente la interfaz tantas o tan pocas veces como desee (para que pueda crear clases que puedan mapear más de una entidad si lo desea) y luego cree otra clase (un proveedor de estrategia) que inyecte una IEnumerablede todas las IEntityMapperStrategies. Ver aquí en 'Tipos especiales'. Inyecte eso en su contexto.
Avi Cherry
22

Esto está usando la última versión actual, beta 8. Pruebe esto:

public class AccountMap
{
    public AccountMap(EntityTypeBuilder<Account> entityBuilder)
    {
        entityBuilder.HasKey(x => x.AccountId);

        entityBuilder.Property(x => x.AccountId).IsRequired();
        entityBuilder.Property(x => x.Username).IsRequired().HasMaxLength(50);
    }
}

Luego, en su DbContext:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        new AccountMap(modelBuilder.Entity<Account>());
    }
Juan
fuente
3
Terminé haciendo algo similar a esto. Sin embargo, decidí usar un método estático en lugar de un constructor.
Matt Sanders
Estoy usando esta metodología y hasta ahora no he tenido problemas, excepto con la herencia. Si quiero heredar el AccountMap en su ejemplo en uno nuevo y agregar una clave alternativa, ¿cuál sería el mejor enfoque?
Chris
14

Puede usar la reflexión para hacer cosas de manera muy similar a cómo funcionan en EF6, con una clase de mapeo separada para cada entidad. Esto funciona en RC1 final:

Primero, cree una interfaz para sus tipos de mapeo:

public interface IEntityTypeConfiguration<TEntityType> where TEntityType : class
{
    void Map(EntityTypeBuilder<TEntityType> builder);
}

Luego cree una clase de mapeo para cada una de sus entidades, por ejemplo, para una Personclase:

public class PersonMap : IEntityTypeConfiguration<Person>
{
    public void Map(EntityTypeBuilder<Person> builder)
    {
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Name).IsRequired().HasMaxLength(100);
    }
}

Ahora, el reflejo mágico OnModelCreatingen su DbContextimplementación:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    // Interface that all of our Entity maps implement
    var mappingInterface = typeof(IEntityTypeConfiguration<>);

    // Types that do entity mapping
    var mappingTypes = typeof(DataContext).GetTypeInfo().Assembly.GetTypes()
        .Where(x => x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));

    // Get the generic Entity method of the ModelBuilder type
    var entityMethod = typeof(ModelBuilder).GetMethods()
        .Single(x => x.Name == "Entity" && 
                x.IsGenericMethod && 
                x.ReturnType.Name == "EntityTypeBuilder`1");

    foreach (var mappingType in mappingTypes)
    {
        // Get the type of entity to be mapped
        var genericTypeArg = mappingType.GetInterfaces().Single().GenericTypeArguments.Single();

        // Get the method builder.Entity<TEntity>
        var genericEntityMethod = entityMethod.MakeGenericMethod(genericTypeArg);

        // Invoke builder.Entity<TEntity> to get a builder for the entity to be mapped
        var entityBuilder = genericEntityMethod.Invoke(builder, null);

        // Create the mapping type and do the mapping
        var mapper = Activator.CreateInstance(mappingType);
        mapper.GetType().GetMethod("Map").Invoke(mapper, new[] { entityBuilder });
    }
}
Cocowalla
fuente
¿Qué referencia usa DataContexty .Where? Hice un proyecto separado para esto y no parece encontrar la referencia.
Ruchan
.Wherees System.Linq, DataContextes la clase donde se agrega el código (mi EF DbContextimpl)
Cocowalla
12

Desde EF Core 2.2 puede agregar todas las configuraciones (clases, que implementaron la interfaz IEntityTypeConfiguration) en una línea en el método OnModelCreating en clase, que se hereda de la clase DbContext

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //this will apply configs from separate classes which implemented IEntityTypeConfiguration<T>
    modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}

Y, como se mencionó en la respuesta anterior, desde EF Core 2.0, puede implementar la interfaz IEntityTypeConfiguration, configurar la configuración de mapeo utilizando FluentAPI en el método Configure.

public class QuestionAnswerConfig : IEntityTypeConfiguration<QuestionAnswer>
{
    public void Configure(EntityTypeBuilder<QuestionAnswer> builder)
    {
      builder
        .HasKey(bc => new { bc.QuestionId, bc.AnswerId });
      builder
        .HasOne(bc => bc.Question)
        .WithMany(b => b.QuestionAnswers)
        .HasForeignKey(bc => bc.QuestionId);
      builder
        .HasOne(bc => bc.Answer)
        .WithMany(c => c.QuestionAnswers)
        .HasForeignKey(bc => bc.AnswerId);
    }
}
Julia Trikoz
fuente
6

Esto es lo que estoy haciendo en un proyecto en el que estoy trabajando actualmente.

public interface IEntityMappingConfiguration<T> where T : class
{
    void Map(EntityTypeBuilder<T> builder);
}

public static class EntityMappingExtensions
{
     public static ModelBuilder RegisterEntityMapping<TEntity, TMapping>(this ModelBuilder builder) 
        where TMapping : IEntityMappingConfiguration<TEntity> 
        where TEntity : class
    {
        var mapper = (IEntityMappingConfiguration<TEntity>)Activator.CreateInstance(typeof (TMapping));
        mapper.Map(builder.Entity<TEntity>());
        return builder;
    }
}

Uso:

En el método OnModelCreating de su contexto:

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder
            .RegisterEntityMapping<Card, CardMapping>()
            .RegisterEntityMapping<User, UserMapping>();
    }

Ejemplo de clase de mapeo:

public class UserMapping : IEntityMappingConfiguration<User>
{
    public void Map(EntityTypeBuilder<User> builder)
    {
        builder.ToTable("User");
        builder.HasKey(m => m.Id);
        builder.Property(m => m.Id).HasColumnName("UserId");
        builder.Property(m => m.FirstName).IsRequired().HasMaxLength(64);
        builder.Property(m => m.LastName).IsRequired().HasMaxLength(64);
        builder.Property(m => m.DateOfBirth);
        builder.Property(m => m.MobileNumber).IsRequired(false);
    }
}

Otra cosa que me gusta hacer para aprovechar el comportamiento de plegado de Visual Studio 2015 es para una Entidad llamada 'Usuario', usted nombra su archivo de mapeo 'User.Mapping.cs', Visual Studio doblará el archivo en el explorador de soluciones para que esté contenido en el archivo de clase de entidad.

Jamie Gould
fuente
Gracias por tu solución. Optimizaré el código de mi solución al final de mi proyecto ... Lo comprobaré con seguridad en el futuro.
Miroslav Siska
¿Solo puedo asumir 'IEntityTypeConfiguration <T>' y Configure(builder)no existí en 2016? Con un ligero cambio de cableado para apuntar a TypeConfiguration, no hay necesidad de una interfaz 'extra'.
WernerCD
3

Terminé con esta solución:

public interface IEntityMappingConfiguration
{
    void Map(ModelBuilder b);
}

public interface IEntityMappingConfiguration<T> : IEntityMappingConfiguration where T : class
{
    void Map(EntityTypeBuilder<T> builder);
}

public abstract class EntityMappingConfiguration<T> : IEntityMappingConfiguration<T> where T : class
{
    public abstract void Map(EntityTypeBuilder<T> b);

    public void Map(ModelBuilder b)
    {
        Map(b.Entity<T>());
    }
}

public static class ModelBuilderExtenions
{
    private static IEnumerable<Type> GetMappingTypes(this Assembly assembly, Type mappingInterface)
    {
        return assembly.GetTypes().Where(x => !x.IsAbstract && x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));
    }

    public static void AddEntityConfigurationsFromAssembly(this ModelBuilder modelBuilder, Assembly assembly)
    {
        var mappingTypes = assembly.GetMappingTypes(typeof (IEntityMappingConfiguration<>));
        foreach (var config in mappingTypes.Select(Activator.CreateInstance).Cast<IEntityMappingConfiguration>())
        {
            config.Map(modelBuilder);
        }
    }
}

Uso de muestra:

public abstract class PersonConfiguration : EntityMappingConfiguration<Person>
{
    public override void Map(EntityTypeBuilder<Person> b)
    {
        b.ToTable("Person", "HumanResources")
            .HasKey(p => p.PersonID);

        b.Property(p => p.FirstName).HasMaxLength(50).IsRequired();
        b.Property(p => p.MiddleName).HasMaxLength(50);
        b.Property(p => p.LastName).HasMaxLength(50).IsRequired();
    }
}

y

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.AddEntityConfigurationsFromAssembly(GetType().Assembly);
}
Domen Kogler
fuente
Recibo un error en tiempo de compilación: " Operator '! X.IsAbstract' no se puede aplicar al operando del tipo 'group group' " en '! X.IsAbstract' (System.Type.IsAbstract) en ModelBuilderExtenions.GetMappingTypes () . ¿Necesito agregar una referencia a mscorlib? ¿Cómo hago eso para un proyecto .NET Core 1.0?
RandyDaddis
para proyectos centrales .net (usando netstandard) necesita usar la extensión GetTypeInfo () en el espacio de nombres System.Reflection. Usar como x.GetTypeInfo (). IsAbstract o x.GetTypeInfo (). GetInterfaces ()
animalito maquina
He usado parte de su solución en la mía y funcionó bien. ¡Gracias!
Diego Cotini
2

Simplemente implemente IEntityTypeConfiguration

public abstract class EntityTypeConfiguration<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : class
{
    public abstract void Configure(EntityTypeBuilder<TEntity> builder);
}

y luego agréguelo a su entidad Contexto

public class ProductContext : DbContext, IDbContext
{
    public ProductContext(DbContextOptions<ProductContext> options)
        : base((DbContextOptions)options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfiguration(new ProductMap());
    }

    public DbSet<Entities.Product> Products { get; set; }
}
Maestro zi
fuente
1

En ef core tenemos que implementar IEntityTypeConfiguration en lugar de EntityTypeConfiguration, en este caso tenemos acceso completo a DbContext modelBuilder y podemos usar una API fluida, pero en ef core esta API es un poco diferente de las versiones anteriores. puede encontrar más detalles sobre la configuración del modelo ef core en

https://www.learnentityframeworkcore.com/configuration/fluent-api

mohammadali ghanbari
fuente
1

En Entity Framework Core 2.0:

Tomé la respuesta de Cocowalla y la adapté para v2.0:

    public static class ModelBuilderExtenions
    {
        private static IEnumerable<Type> GetMappingTypes(this Assembly assembly, Type mappingInterface)
        {
            return assembly.GetTypes().Where(x => !x.IsAbstract && x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));
        }

        public static void AddEntityConfigurationsFromAssembly(this ModelBuilder modelBuilder, Assembly assembly)
        {
            // Types that do entity mapping
            var mappingTypes = assembly.GetMappingTypes(typeof(IEntityTypeConfiguration<>));

            // Get the generic Entity method of the ModelBuilder type
            var entityMethod = typeof(ModelBuilder).GetMethods()
                .Single(x => x.Name == "Entity" &&
                        x.IsGenericMethod &&
                        x.ReturnType.Name == "EntityTypeBuilder`1");

            foreach (var mappingType in mappingTypes)
            {
                // Get the type of entity to be mapped
                var genericTypeArg = mappingType.GetInterfaces().Single().GenericTypeArguments.Single();

                // Get the method builder.Entity<TEntity>
                var genericEntityMethod = entityMethod.MakeGenericMethod(genericTypeArg);

                // Invoke builder.Entity<TEntity> to get a builder for the entity to be mapped
                var entityBuilder = genericEntityMethod.Invoke(modelBuilder, null);

                // Create the mapping type and do the mapping
                var mapper = Activator.CreateInstance(mappingType);
                mapper.GetType().GetMethod("Configure").Invoke(mapper, new[] { entityBuilder });
            }
        }


    }

Y se usa en el DbContext así:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.AddEntityConfigurationsFromAssembly(GetType().Assembly);
    }

Y así es como se crea una configuración de tipo de entidad para una entidad:

    public class UserUserRoleEntityTypeConfiguration : IEntityTypeConfiguration<UserUserRole>
    {
        public void Configure(EntityTypeBuilder<UserUserRole> builder)
        {
            builder.ToTable("UserUserRole");
            // compound PK
            builder.HasKey(p => new { p.UserId, p.UserRoleId });
        }
    }
Francisco Goldenstein
fuente
No funciono para mi. Excepción:Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
Tohid
PD: Encontré la solución: &&! T.IsGenericType. Porque tenía una clase base que es genérica ( class EntityTypeConfigurationBase<TEntity> : IEntityTypeConfiguration<TEntity>). No puede hacer una instancia de esta clase base.
Tohid
0

Estoy en lo cierto?

public class SmartModelBuilder<T> where T : class         {

    private ModelBuilder _builder { get; set; }
    private Action<EntityTypeBuilder<T>> _entityAction { get; set; }

    public SmartModelBuilder(ModelBuilder builder, Action<EntityTypeBuilder<T>> entityAction)
    {
        this._builder = builder;
        this._entityAction = entityAction;

        this._builder.Entity<T>(_entityAction);
    }
}   

Puedo pasar config:

 protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);



        new SmartModelBuilder<Blog>(builder, entity => entity.Property(b => b.Url).Required());

    } 
Miroslav Siska
fuente
La respuesta aceptada parece mejor que esto. Ambos tienen el mismo efecto secundario negativo de tener un OnModelCreating () desordenado masivamente, pero la respuesta aceptada no requiere ninguna clase auxiliar. ¿Me falta algo que mejore su respuesta?
Vela Judo
0

Seguí un enfoque similar a la forma en que Microsoft implementó ForSqlServerToTable

utilizando el método de extensión ...

el indicador parcial es obligatorio si desea usar el mismo nombre de clase en varios archivos

public class ConsignorUser
{
    public int ConsignorId { get; set; }

    public string UserId { get; set; }

    public virtual Consignor Consignor { get; set; }
    public virtual User User { get; set; }

}

public static partial class Entity_FluentMappings
{
    public static EntityTypeBuilder<ConsignorUser> AddFluentMapping<TEntity> (
        this EntityTypeBuilder<ConsignorUser> entityTypeBuilder) 
        where TEntity : ConsignorUser
    {
       entityTypeBuilder.HasKey(x => new { x.ConsignorId, x.UserId });
       return entityTypeBuilder;
    }      
}

Luego, en DataContext OnModelCreating, haga su llamada para cada extensión ...

 public class DataContext : IdentityDbContext<User>
{

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);

        builder.Entity<ConsignorUser>().AddFluentMapping<ConsignorUser>();
        builder.Entity<DealerUser>().AddFluentMapping<DealerUser>();           

    }

De esta manera, seguimos el mismo patrón utilizado por los otros métodos de construcción.

¿Qué piensas?

Joseph Bailey
fuente
0

Bueno, aquí está el problema para la mejora en el repositorio de EF7 Github: https://github.com/aspnet/EntityFramework/issues/2805

Puede rastrear el problema directamente allí, aunque todavía está solo en la cartera sin prioridad designada.

Denis Biondic
fuente
Debería haber un problema como "Deja de romper cosas bien diseñadas".
Kamil
0

Tengo un proyecto que le permite configurar entidades fuera de DbContext.OnModelCreatingUsted configura cada entidad en una clase separada que hereda deStaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration

Primero debe crear una clase que herede de StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration<TEntity>dónde TEntityes la clase que desea configurar.

using StaticDotNet.EntityFrameworkCore.ModelConfiguration;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

public class ExampleEntityConfiguration
    : EntityTypeConfiguration<ExampleEntity>
{
    public override void Configure( EntityTypeBuilder<ExampleEntity> builder )
    {
        //Add configuration just like you do in DbContext.OnModelCreating
    }
}

Luego, en su clase de inicio, solo necesita decirle a Entity Framework dónde encontrar todas sus clases de configuración cuando configura su DbContext.

using StaticDotNet.EntityFrameworkCore.ModelConfiguration;

public void ConfigureServices(IServiceCollection services)
{
    Assembly[] assemblies = new Assembly[]
    {
        // Add your assembiles here.
    };

    services.AddDbContext<ExampleDbContext>( x => x
        .AddEntityTypeConfigurations( assemblies )
    );
}

También hay una opción para agregar configuraciones de tipo usando un proveedor. El repositorio tiene documentación completa sobre cómo usarlo.

https://github.com/john-t-white/StaticDotNet.EntityFrameworkCore.ModelConfiguration

John White
fuente
No publique la misma respuesta a varias preguntas. Si la misma información realmente responde a ambas preguntas, entonces una pregunta (generalmente la más nueva) debería cerrarse como un duplicado de la otra. Puede indicar esto votando para cerrarlo como un duplicado o, si no tiene suficiente reputación para eso, levante una bandera para indicar que es un duplicado. De lo contrario, asegúrese de adaptar su respuesta a esta pregunta y no solo pegue la misma respuesta en varios lugares.
elixenide