EF Core Mapping EntityTypeConfiguration


129

In EF6 di solito siamo in grado di utilizzare questo modo per configurare Entity.

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

Come possiamo fare in EF Core, da quando la classe I Inherit EntityTypeConfiguration che non è in grado di trovare la classe.

Ho scaricato il codice sorgente raw EF Core da GitHub, non riesco a trovarlo. Qualcuno può aiutare su questo?


8
Perché non accettare quella risposta?
Den

da quando è in beta5 ora, quando mettiamo maxLength (50). nel db genera nvarchar (max)
Herman

6
Per chiunque sia interessato a questo, ora esiste un metodo IEntityTypeConfiguration<T>con un solo void Configure()metodo che puoi implementare. Dettagli qui: github.com/aspnet/EntityFramework/pull/6989
Galilyou

Risposte:


183

Da EF Core 2.0 esiste IEntityTypeConfiguration<TEntity>. Puoi usarlo in questo modo:

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

Maggiori informazioni su questa e altre nuove funzionalità introdotte nella 2.0 possono essere trovate qui .


8
Questa è la risposta migliore per EF Core 2.0. Grazie!
Collin M. Barrett

2
Questo è eccellente. Stavo cercando un modo per separare le definizioni API fluenti. Grazie
Blaze

Si veda anche questa risposta per "ToTable" e "HasColumnName", ecc ::: stackoverflow.com/questions/43200184/...
granadaCoder

se hai una configurazione personalizzata uomo, basta metterla builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);applicherà tutte le conferme personalizzate
alim91

52

Puoi ottenere ciò attraverso alcuni semplici tipi aggiuntivi:

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

1
Dov'è ForSqlServerToTable()?
im1dermike


1
Come usare HasColumnType con questo? . Per es. entity.Property(c => c.JoinDate).HasColumnType("date");
Biju Soman

OnModelCreatingè stato aggiornato per richiedere un file DbModelBuilder. Il modo per aggiungere configurazioni a questo è oramodelBuilder.Configurations.Add(new UserConfiguration());
Izzy

2
@Izzy - DbModelBuilder è Entity Framework 6.0, ModelBuilder è EF Core. Sono assembly diversi e in questo caso la domanda era specifica per EF Core.
Jason

29

In EF7, esegui l'override di OnModelCreating sulla classe DbContext che stai 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);
    }

23
Quindi, se ho 20 configurazioni di tipi di entità, le inserisco in un unico metodo enorme?
Den

6
Per impostazione predefinita, sembra così. Puoi creare le tue classi FooMapper / FooModelBuilder che estendono una classe base e ha un metodo per passare un EntityBuilder <Foo> digitato. Potresti anche usare la nuova iniezione delle dipendenze e l'interfaccia IConfiguration per farle scoprire / chiamare automaticamente, se vuoi essere fantasioso!
Avi Cherry

1
Prego. Votare una risposta (e incoraggiare l'interlocutore ad accettarla) è ancora meglio!
Avi Cherry

Di solito lo faccio :)
Den

4
Vuoi provare i nuovi strumenti di inserimento delle dipendenze? Crea IEntityMapperStrategyun'interfaccia con una void MapEntity(ModelBuilder, Type)firma e bool IsFor(Type). Implementa l'interfaccia tutte le volte che vuoi (in modo da poter creare classi che possono mappare più di un'entità se vuoi) e quindi crea un'altra classe (un provider di strategia) che inietta uno IEnumerabledi tutti i file IEntityMapperStrategies. Vedi qui sotto "Tipi speciali". Iniettalo nel tuo contesto.
Avi Cherry

22

Questo utilizza l'ultima versione beta 8. Prova questo:

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

Quindi nel tuo DbContext:

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

        new AccountMap(modelBuilder.Entity<Account>());
    }

3
Ho finito per fare qualcosa di simile a questo. Tuttavia, ho deciso di utilizzare un metodo statico invece di un costruttore.
Matt Sanders

Sto usando questa metodologia e fino a questo punto non ho avuto problemi se non con l'ereditarietà. Se voglio ereditare l'AccountMap nel tuo esempio in uno nuovo e aggiungere una chiave alternativa, quale sarebbe l'approccio migliore?
chris

14

Puoi usare la reflection per eseguire operazioni in modo molto simile a come funzionano in EF6, con una classe di mapping separata per ogni entità. Funziona nella finale RC1:

Innanzitutto, crea un'interfaccia per i tuoi tipi di mappatura:

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

Quindi crea una classe di mappatura per ciascuna delle tue entità, ad esempio per una 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);
    }
}

Ora, la magia del riflesso OnModelCreatingnella tua DbContextimplementazione:

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

Quale riferimento utilizza DataContexte .Where? Ho fatto un progetto separato per questo e non sembra trovare il riferimento.
Ruchan

.Whereè System.Linq, DataContextè la classe in cui viene aggiunto il codice (my EF DbContext
impl

12

A partire da EF Core 2.2 è possibile aggiungere tutte le configurazioni (classi, che hanno implementato l'interfaccia IEntityTypeConfiguration) in una riga nel metodo OnModelCreating nella classe, che viene ereditato dalla classe DbContext

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

E, come è stato menzionato nella risposta precedente, a partire da EF Core 2.0, è possibile implementare l'interfaccia IEntityTypeConfiguration, configurare la configurazione della mappatura utilizzando FluentAPI nel metodo 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);
    }
}

6

Questo è quello che sto facendo in un progetto a cui sto lavorando.

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:

Nel metodo OnModelCreating del tuo contesto:

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

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

Classe di mappatura di esempio:

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

Un'altra cosa che mi piace fare per sfruttare il comportamento pieghevole di Visual Studio 2015 è per un'entità chiamata "Utente", denomina il tuo file di mappatura "User.Mapping.cs", Visual Studio piegherà il file in Esplora soluzioni in modo che sia contenuto nel file della classe entità.


Grazie per la tua soluzione. Ottimizzerò il codice della mia soluzione alla fine del mio progetto ... Lo verificherò sicuramente in futuro.
Miroslav Siska

Posso solo supporre "IEntityTypeConfiguration <T>" e Configure(builder)non esisteva nel 2016? Con una leggera modifica del cablaggio per puntare a TypeConfiguration, non è necessaria l'interfaccia "extra".
WernerCD il

3

Ho concluso con questa soluzione:

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 del campione:

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

Ricevo un errore in fase di compilazione: "L' operatore '! X.IsAbstract' non può essere applicato all'operando di tipo 'gruppo di metodi' " su '! X.IsAbstract' (System.Type.IsAbstract) in ModelBuilderExtenions.GetMappingTypes () . Devo aggiungere un riferimento a mscorlib? Come posso farlo a un progetto .NET Core 1.0?
RandyDaddis

per i progetti .net core (utilizzando netstandard) è necessario utilizzare l'estensione GetTypeInfo () nello spazio dei nomi System.Reflection. Usa come x.GetTypeInfo (). IsAbstract o x.GetTypeInfo (). GetInterfaces ()
animalito maquina

Ho usato parte della tua soluzione sulla mia e ha funzionato bene. Grazie!
Diego Cotini

2

Basta implementare IEntityTypeConfiguration

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

e quindi aggiungilo alla tua entità Context

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


1

In Entity Framework Core 2.0:

Ho preso la risposta di Cocowalla e l'ho adattata per 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 });
            }
        }


    }

Ed è usato nel DbContext in questo modo:

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

Ed è così che crei una configurazione del tipo di entità per un'entità:

    public class UserUserRoleEntityTypeConfiguration : IEntityTypeConfiguration<UserUserRole>
    {
        public void Configure(EntityTypeBuilder<UserUserRole> builder)
        {
            builder.ToTable("UserUserRole");
            // compound PK
            builder.HasKey(p => new { p.UserId, p.UserRoleId });
        }
    }

Non ha funzionato per me. Eccezione:Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
Tohid

PS: trovata la soluzione: &&! T.IsGenericType. Perché avevo una classe base generica ( class EntityTypeConfigurationBase<TEntity> : IEntityTypeConfiguration<TEntity>). Non puoi creare un'istanza di questa classe base.
Tohid

0

Ho ragione?

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

Posso passare la configurazione:

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

    } 

La risposta accettata sembra migliore di questa. Entrambi hanno lo stesso effetto collaterale negativo di avere un OnModelCreating () enormemente disordinato, ma la risposta accettata non richiede alcuna classe di supporto. C'è qualcosa che mi manca che la tua risposta migliori?
Sailing Judo

0

Ho seguito un approccio simile al modo in cui Microsoft ha implementato ForSqlServerToTable

utilizzando il metodo di estensione ...

il flag parziale è necessario se si desidera utilizzare lo stesso nome di classe in più file

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

Quindi nel DataContext OnModelCreating fai la tua chiamata per ogni interno ...

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

    }

In questo modo stiamo seguendo lo stesso modello usato dagli altri metodi del builder.

Cosa ne pensi?



0

Ho un progetto che ti consente di configurare entità al di fuori di DbContext.OnModelCreatingConfigura ogni entità in una classe separata da cui ereditaStaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration

Per prima cosa è necessario creare una classe che erediti da StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration<TEntity>dove si TEntitytrova la classe che si desidera configurare.

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

Quindi nella tua classe Startup devi solo dire a Entity Framework dove trovare tutte le tue classi di configurazione quando stai configurando il tuo 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 )
    );
}

C'è anche un'opzione per aggiungere configurazioni di tipo utilizzando un provider. Il repo ha una documentazione completa su come usarlo.

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


Si prega di non pubblicare la stessa risposta a più domande. Se le stesse informazioni rispondono davvero a entrambe le domande, allora una domanda (di solito quella più recente) dovrebbe essere chiusa come un duplicato dell'altra. Puoi indicarlo votando per chiuderlo come duplicato o, se non hai abbastanza reputazione per questo, alzare una bandiera per indicare che è un duplicato. Altrimenti, assicurati di adattare la risposta a questa domanda e non incollare la stessa risposta in più punti.
elixenide
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.