EF Core Mapping EntityTypeConfiguration

129

No EF6, geralmente podemos usar esse caminho para configurar a Entidade.

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

Como podemos fazer no EF Core, desde quando a classe I Herda EntityTypeConfiguration que não consegue encontrar a classe.

Eu faço o download do código-fonte bruto EF Core do GitHub, não consigo encontrá-lo. Alguém pode ajudar nisso?

Herman
fonte
8
Por que não aceitar essa resposta?
Den
já que está no beta5 agora, quando colocamos maxLength (50). no db ele gera nvarchar (max)
Herman
6
Para qualquer pessoa interessada nisso, agora existe IEntityTypeConfiguration<T>um void Configure()método com um que você pode implementar. Detalhes aqui: github.com/aspnet/EntityFramework/pull/6989
Galilyou

Respostas:

183

Desde o EF Core 2.0, existe IEntityTypeConfiguration<TEntity>. Você pode usá-lo assim:

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());

Mais sobre este e outros novos recursos introduzidos na 2.0 podem ser encontrados aqui .

Krzysztof Branicki
fonte
8
Esta é a melhor resposta para o EF Core 2.0. Obrigado!
Collin M. Barrett
2
Isto e excelente. Eu estava procurando uma maneira de separar definições de API fluentes. Obrigado
Blaze
Consulte também esta resposta para "ToTable" e "HasColumnName", etc ::: stackoverflow.com/questions/43200184/…
granadaCoder
se você tiver a configuração personalizada do homem, basta builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);aplicar todas as confirmações personalizadas
alim91 24/07
52

Você pode conseguir isso através de alguns tipos adicionais 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
fonte
1
Onde está ForSqlServerToTable()?
im1dermike
1
Como usar o HasColumnType com isso? . Por exemplo. entity.Property(c => c.JoinDate).HasColumnType("date");
Biju Soman
OnModelCreatingfoi atualizado para exigir a DbModelBuilder. A maneira de adicionar configurações a isso agora émodelBuilder.Configurations.Add(new UserConfiguration());
Izzy
2
@Izzy - DbModelBuilder é Entity Framework 6.0, ModelBuilder é EF Core. São assembléias diferentes e, nesse caso, a questão era específica do EF Core.
Jason
29

No EF7, você substitui OnModelCreating na classe 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
fonte
23
Então, se eu tenho 20 configurações de tipo de entidade, coloco-as em um método enorme?
Den
6
Por padrão, parece que sim. Você pode criar suas próprias classes FooMapper / FooModelBuilder que estendem uma classe base e têm um método para passar um EntityBuilder <Foo> digitado. Você pode até usar a nova interface de injeção de dependência e IConfiguration para que eles sejam descobertos / chamados automaticamente, se você quiser ser chique!
Avi Cereja
1
De nada. Aumentar a votação de uma resposta (e incentivar o interlocutor a aceitá-la) é ainda melhor!
Avi Cherry
Eu costumo fazer isso :)
Den
4
Experimente as novas ferramentas de injeção de dependência? Faça uma IEntityMapperStrategyinterface com uma void MapEntity(ModelBuilder, Type)assinatura e bool IsFor(Type). Implemente a interface quantas vezes quiser (para que você possa criar classes que podem mapear mais de uma entidade, se desejar) e, em seguida, criar outra classe (um provedor de estratégia) que injete IEnumerabletudo isso IEntityMapperStrategies. Veja aqui em 'Tipos especiais'. Injete isso no seu contexto.
Avi Cereja
22

Isso está usando a versão mais recente atual, beta 8. Tente o seguinte:

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

Em seguida, no seu DbContext:

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

        new AccountMap(modelBuilder.Entity<Account>());
    }
John
fonte
3
Acabei fazendo semelhante a isso. Decidi usar um método estático em vez de um construtor.
9788102Código
Estou usando essa metodologia e, até o momento, não tive problemas, exceto com herança. Se eu quiser herdar o AccountMap do seu exemplo em um novo e adicionar uma chave alternativa - qual seria a melhor abordagem?
Chris
14

Você pode usar a reflexão para fazer as coisas de maneira muito semelhante à maneira como elas funcionam no EF6, com uma classe de mapeamento separada para cada entidade. Isso funciona na RC1 final:

Primeiro, crie uma interface para seus tipos de mapeamento:

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

Em seguida, crie uma classe de mapeamento para cada uma de suas entidades, por exemplo, para uma Personclasse:

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

Agora, a reflexão é mágica OnModelCreatingna sua DbContextimplementação:

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
fonte
Que referência o DataContexte .Whereusa? Eu fiz um projeto separado para isso e não parece encontrar a referência.
Ruchan 01/06
.Whereé System.Linq, DataContexté a classe em que é adicionado o código (minha EF DbContextimpl)
Cocowalla
12

Desde o EF Core 2.2, você pode adicionar todas as configurações (classes, que implementaram a interface IEntityTypeConfiguration) em uma linha no método OnModelCreating na classe, herdada da classe DbContext

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

E, como mencionado na resposta anterior, desde o EF Core 2.0, você pode implementar a interface IEntityTypeConfiguration, configurar a configuração de mapeamento usando o FluentAPI no 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
fonte
6

É isso que estou fazendo em um projeto no qual estou trabalhando atualmente.

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:

No método OnModelCreating do seu contexto:

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

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

Exemplo de classe de mapeamento:

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

Outra coisa que gosto de fazer para aproveitar o comportamento de dobramento do Visual Studio 2015 é para uma Entidade chamada 'Usuário', você nomeia seu arquivo de mapeamento como 'User.Mapping.cs', o Visual Studio dobrará o arquivo no explorador de soluções para que ele esteja contido no arquivo de classe da entidade.

Jamie Gould
fonte
Obrigado pela sua solução. Otimizarei o código da minha solução no final do meu projeto ... Verificarei com certeza no futuro.
Miroslav Siska
Só posso assumir 'IEntityTypeConfiguration <T>' e Configure(builder)não existia em 2016? Com uma ligeira mudança de fiação para apontar para TypeConfiguration, não há necessidade da interface 'extra'.
WernerCD
3

Eu terminei com esta solução:

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 da amostra:

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

e

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.AddEntityConfigurationsFromAssembly(GetType().Assembly);
}
Domen Kogler
fonte
Estou recebendo um erro em tempo de compilação: "O operador '! X.IsAbstract' não pode ser aplicado ao operando do tipo 'grupo de métodos' " em '! X.IsAbstract' (System.Type.IsAbstract) em ModelBuilderExtenions.GetMappingTypes () . Preciso adicionar uma referência ao mscorlib? Como faço isso para um projeto do .NET Core 1.0?
RandyDaddis 13/09/16
para projetos principais do .net (usando netstandard), você precisa usar a extensão GetTypeInfo () no espaço para nome System.Reflection. Use como x.GetTypeInfo (). IsAbstract ou x.GetTypeInfo (). GetInterfaces ()
maquina
Eu usei parte da sua solução na minha e funcionou bem. Obrigado!
Diego Cotini
2

Basta implementar o IEntityTypeConfiguration

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

e adicione-o à sua entidade

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; }
}
Master Zi
fonte
1

No ef core, temos de impulsionar o IEntityTypeConfiguration em vez do EntityTypeConfiguration. Nesse caso, temos acesso total ao DbContext modelBuilder e podemos usar API fluente, mas, no núcleo, essa API é um pouco diferente das versões anteriores. você pode encontrar mais detalhes sobre a configuração do modelo principal ef em

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

mohammadali ghanbari
fonte
1

No Entity Framework Core 2.0:

Peguei a resposta de Cocowalla e a adaptei para a 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 });
            }
        }


    }

E é usado no DbContext assim:

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

E é assim que você cria uma configuração de tipo de entidade para uma entidade:

    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
fonte
Não funcionou para mim. Exceção:Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
Tohid 14/01/19
PS: Encontrei a solução: &&! T.IsGenericType. Porque eu tinha uma classe base que é genérica ( class EntityTypeConfigurationBase<TEntity> : IEntityTypeConfiguration<TEntity>). Você não pode criar uma instância dessa classe base.
Tohid
0

Estou certo?

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

Eu posso passar a configuração:

 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
fonte
A resposta aceita parece melhor que isso. Ambos têm o mesmo efeito colateral negativo de ter um OnModelCreating () bastante confuso, mas a resposta aceita não requer nenhuma classe auxiliar. Falta alguma coisa que sua resposta melhore?
Sailing Judo
0

Eu segui uma abordagem semelhante à maneira como a Microsoft implementou o ForSqlServerToTable

usando o método de extensão ...

o sinalizador parcial é necessário se você deseja usar o mesmo nome de classe em vários arquivos

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

Em seguida, no DataContext OnModelCreating, faça sua chamada para cada ramal ...

 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>();           

    }

Desta forma, estamos seguindo o mesmo padrão usado pelos outros métodos do construtor.

O que você acha?

Joseph Bailey
fonte
0

Bem, aqui está o problema do aprimoramento no repo do EF7 Github: https://github.com/aspnet/EntityFramework/issues/2805

Você pode rastrear o problema diretamente lá, apesar de ainda estar em lista de pendências sem prioridade designada.

Denis Biondic
fonte
Deve haver um problema como "Pare de quebrar coisas bem projetadas".
Kamil
0

Eu tenho um projeto que permite configurar entidades fora do DbContext.OnModelCreatingVocê configura cada entidade em uma classe separada que herda deStaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration

Primeiro, você precisa criar uma classe que herda de StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration<TEntity>onde TEntityestá a classe que deseja 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
    }
}

Em sua classe Startup, você só precisa informar ao Entity Framework onde encontrar todas as suas classes de configuração quando estiver configurando seu 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 )
    );
}

Há também uma opção para adicionar configurações de tipo usando um provedor. O repositório possui documentação completa sobre como usá-lo.

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

John White
fonte
Não publique a mesma resposta em várias perguntas. Se a mesma informação realmente responder às duas perguntas, uma pergunta (geralmente a mais recente) deve ser fechada como duplicata da outra. Você pode indicar isso votando para fechá-lo como duplicado ou, se você não tiver reputação suficiente para isso, levante uma bandeira para indicar que é uma duplicata. Caso contrário, certifique-se de adaptar sua resposta a esta pergunta e não cole a mesma resposta em vários locais.
Elixenide