EF Core Mapping EntityTypeConfiguration


129

Dans EF6, nous pouvons généralement utiliser cette méthode pour configurer l'entité.

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

Comment nous pouvons faire dans EF Core, depuis quand la classe I hérite EntityTypeConfiguration qui ne parvient pas à trouver la classe.

Je télécharge le code source brut d'EF Core à partir du GitHub, je ne le trouve pas. Quelqu'un peut-il aider à ce sujet?


8
Pourquoi ne pas accepter cette réponse?
Den

depuis il en beta5 maintenant, quand on met maxLength (50). dans la base de données, il génère nvarchar (max)
Herman

6
Pour toute personne intéressée par cela, il existe maintenant une méthode IEntityTypeConfiguration<T>unique void Configure()que vous pouvez implémenter. Détails ici: github.com/aspnet/EntityFramework/pull/6989
Galilyou

Réponses:


183

Depuis EF Core 2.0, il existe IEntityTypeConfiguration<TEntity>. Vous pouvez l'utiliser comme ceci:

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

Pour en savoir plus sur ceci et sur d'autres nouvelles fonctionnalités introduites dans la version 2.0, cliquez ici .


8
C'est la meilleure réponse pour EF Core 2.0. Merci!
Collin M. Barrett

2
C'est excellent. Je cherchais un moyen de séparer les définitions d'API fluides. Merci
Blaze

Voir également cette réponse pour "ToTable" et "HasColumnName", etc ::: stackoverflow.com/questions/43200184/…
granadaCoder

si vous avez une configuration personnalisée de l'homme, il suffit de mettre builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);cela pour appliquer toutes les confirgurations personnalisées
alim91

52

Vous pouvez y parvenir grâce à quelques types supplémentaires 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);
}

Usage:

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
Où est ForSqlServerToTable()?
im1dermike


1
Comment utiliser HasColumnType avec ça? . Pour par exemple. entity.Property(c => c.JoinDate).HasColumnType("date");
Biju Soman

OnModelCreatinga été mis à jour pour exiger un DbModelBuilder. Le moyen d'ajouter des configurations à cela est maintenantmodelBuilder.Configurations.Add(new UserConfiguration());
Izzy

2
@Izzy - DbModelBuilder est Entity Framework 6.0, ModelBuilder est EF Core. Ce sont des assemblys différents et dans ce cas, la question était spécifique à EF Core.
Jason

29

Dans EF7, vous substituez OnModelCreating sur la classe DbContext que vous implémentez.

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
Donc, si j'ai 20 configurations de type d'entité, je les mets dans une énorme méthode?
Den

6
Par défaut, il semble que oui. Vous pouvez créer vos propres classes FooMapper / FooModelBuilder qui étendent une classe de base et ont une méthode que vous passez un EntityBuilder <Foo> typé. Vous pouvez même utiliser la nouvelle interface d'injection de dépendances et IConfiguration pour les faire découvrir / appeler automatiquement pour vous, si vous vouliez être sophistiqué!
Avi Cherry

1
De rien. Voter une réponse (et encourager le questionneur à l'accepter) est encore mieux!
Avi Cherry

Je fais habituellement ça :)
Den

4
Essayez les nouveaux outils d'injection de dépendances? Créez une IEntityMapperStrategyinterface avec une void MapEntity(ModelBuilder, Type)signature et bool IsFor(Type). Implémentez l'interface autant ou aussi peu de fois que vous le souhaitez (afin que vous puissiez créer des classes pouvant mapper plusieurs entités si vous le souhaitez), puis créez une autre classe (un fournisseur de stratégie) qui injecte un IEnumerablede tous les IEntityMapperStrategies. Voir ici sous «Types spéciaux». Injectez cela dans votre contexte.
Avi Cherry

22

Ceci utilise la dernière version actuelle, la version bêta 8. Essayez ceci:

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

Puis dans votre DbContext:

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

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

3
J'ai fini par faire la même chose. J'ai décidé d'utiliser une méthode statique au lieu d'un constructeur.
Matt Sanders

J'utilise cette méthodologie et jusqu'à présent, je n'ai eu aucun problème sauf avec l'héritage. Si je veux hériter du AccountMap de votre exemple dans un nouveau et ajouter une clé alternative, quelle serait la meilleure approche?
chris

14

Vous pouvez utiliser la réflexion pour faire les choses de manière très similaire à la façon dont elles fonctionnent dans EF6, avec une classe de mappage distincte pour chaque entité. Cela fonctionne en RC1 final:

Tout d'abord, créez une interface pour vos types de mappage:

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

Créez ensuite une classe de mappage pour chacune de vos entités, par exemple pour une 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);
    }
}

Maintenant, la magie du reflet OnModelCreatingdans votre DbContextimplémentation:

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

Quelle référence utilise le DataContextet .Where? J'ai fait un projet séparé pour cela et ne semble pas trouver la référence.
Ruchan

.Whereest System.Linq, DataContextest la classe où le code est ajouté (mon EF DbContextimpl)
Cocowalla

12

Depuis EF Core 2.2, vous pouvez ajouter toutes les configurations (classes, qui ont implémenté l'interface IEntityTypeConfiguration) dans une ligne dans la méthode OnModelCreating in class, qui est héritée de la classe DbContext

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

Et, comme cela a été mentionné dans la réponse précédente, depuis EF Core 2.0, vous pouvez implémenter l'interface IEntityTypeConfiguration, configurer la configuration du mappage avec l'utilisation de FluentAPI dans la méthode 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

C'est ce que je fais dans un projet sur lequel je travaille actuellement.

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

Usage:

Dans la méthode OnModelCreating de votre contexte:

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

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

Exemple de classe de mappage:

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

Une autre chose que j'aime faire pour profiter du comportement de pliage de Visual Studio 2015 est pour une entité appelée `` User '', vous nommez votre fichier de mappage `` User.Mapping.cs '', Visual Studio pliera le fichier dans l'explorateur de solutions afin qu'il soit contenu dans le fichier de classe d'entité.


Merci pour votre solution. J'optimiserai mon code de solution à la fin de mon projet ... Je le vérifierai bien à l'avenir.
Miroslav Siska

Je ne peux supposer que 'IEntityTypeConfiguration <T>' et Configure(builder)n'existait pas en 2016? Avec un léger changement de câblage pour pointer vers TypeConfiguration, il n'est pas nécessaire d'utiliser l'interface «supplémentaire».
WernerCD le

3

J'ai terminé avec cette solution:

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

Exemple d'utilisation:

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

et

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

J'obtiens une erreur de compilation: "L' opérateur '! X.IsAbstract' ne peut pas être appliqué à l'opérande de type 'groupe de méthodes' " sur '! X.IsAbstract' (System.Type.IsAbstract) dans ModelBuilderExtenions.GetMappingTypes () . Dois-je ajouter une référence à mscorlib? Comment faire cela à un projet .NET Core 1.0?
RandyDaddis

pour les projets principaux .net (utilisant netstandard), vous devez utiliser l'extension GetTypeInfo () dans l'espace de noms System.Reflection. Utiliser comme x.GetTypeInfo (). IsAbstract ou x.GetTypeInfo (). GetInterfaces ()
animalito maquina

J'ai utilisé une partie de votre solution sur la mienne et cela a bien fonctionné. Merci!
Diego Cotini

2

Implémentez simplement IEntityTypeConfiguration

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

puis ajoutez-le à votre contexte d'entité

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

Dans ef core, nous devons implémenter IEntityTypeConfiguration au lieu de EntityTypeConfiguration.Dans ce cas, nous avons un accès complet à DbContext modelBuilder et nous pouvons utiliser une API fluide, mais dans ef core, cette API est un peu différente des versions précédentes. vous pouvez trouver plus de détails sur la configuration du modèle ef core sur

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


1

Dans Entity Framework Core 2.0:

J'ai pris la réponse de Cocowalla et l'ai adaptée pour la 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 });
            }
        }


    }

Et il est utilisé dans le DbContext comme ceci:

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

Et voici comment créer une configuration de type d'entité pour une 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 });
        }
    }

Cela n'a pas fonctionné pour moi. Exception:Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
Tohid le

PS: J'ai trouvé la solution: &&! T.IsGenericType. Parce que j'avais une classe de base générique ( class EntityTypeConfigurationBase<TEntity> : IEntityTypeConfiguration<TEntity>). Vous ne pouvez pas créer une instance de cette classe de base.
Tohid le

0

Ai-je raison?

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

Je peux passer la configuration:

 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 réponse acceptée semble meilleure que cela. Les deux ont le même effet secondaire négatif d'avoir un OnModelCreating () massivement encombré, mais la réponse acceptée ne nécessite aucune classe d'assistance. Y a-t-il quelque chose qui me manque pour que votre réponse s'améliore?
Sailing Judo

0

J'ai suivi une approche similaire à la façon dont Microsoft a implémenté ForSqlServerToTable

en utilisant la méthode d'extension ...

l' indicateur partiel est requis si vous souhaitez utiliser le même nom de classe dans plusieurs fichiers

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

Ensuite, dans le DataContext OnModelCreating, faites votre appel pour chaque extension ...

 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 cette façon, nous suivons le même modèle utilisé par les autres méthodes de construction.

Que pensez-vous?



0

J'ai un projet qui vous permet de configurer des entités en dehors de la DbContext.OnModelCreatingVous configurez chaque entité dans une classe distincte qui hérite deStaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration

Vous devez d'abord créer une classe qui hérite d' StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration<TEntity>où se TEntitytrouve la classe que vous souhaitez configurer.

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

Ensuite, dans votre classe de démarrage, il vous suffit d'indiquer à Entity Framework où trouver toutes vos classes de configuration lorsque vous configurez votre 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 )
    );
}

Il existe également une option pour ajouter des configurations de type à l'aide d'un fournisseur. Le dépôt a une documentation complète sur son utilisation.

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


Veuillez ne pas publier la même réponse à plusieurs questions. Si la même information répond vraiment aux deux questions, alors une question (généralement la plus récente) doit être fermée comme un double de l'autre. Vous pouvez l'indiquer en votant pour le fermer comme un doublon ou, si vous n'avez pas assez de réputation pour cela, lever un drapeau pour indiquer qu'il s'agit d'un doublon. Sinon, assurez-vous d'adapter votre réponse à cette question et ne collez pas simplement la même réponse à plusieurs endroits.
elixenide
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.