web-dev-qa-db-fra.com

Registre en bloc IEntityTypeConfiguration <> noyau de structure d'entité

Ok, je me sers donc d’une structure d’entité avec les migrations en premier point et en code. Ce n'est pas un problème en tant que tel, je me demandais simplement si quelqu'un avait trouvé un meilleur moyen de le faire.

Actuellement, j'ai de nombreuses configurations de type d'entité comme

public class ExampleEntityConfiguration : IEntityTypeConfiguration<ExampleEntity>
{
   public void Configure(EntityTypeBuilder<ExampleEntity> builder)
   {
      builder.Property(p => p.Id).ValueGeneratedNever();

      // more options here
   }
}

et je les enregistre dans mon dbcontext comme si

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

  modelBuilder.ApplyConfiguration(new ExampleEntityConfiguration());

  // lot's more configurations here
}

quelqu'un a-t-il rencontré ou connaît-il un moyen d'enregistrer toutes les interfaces IEntityTypeConfiguration

Cela ressemble à beaucoup de code répétitif qui pourrait être résolu en obtenant une liste des configurations, en les parcourant et en les appliquant dans le contexte. Je ne sais tout simplement pas par où commencer pour obtenir une liste de classes IEntityTypeConfiguration qui existent dans un espace de noms particulier.

Toute aide/suggestion serait génial.

11
tjackadams

Cela peut être fait avec une réflexion comme celle-ci:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);    
    // get ApplyConfiguration method with reflection
    var applyGenericMethod = typeof(ModelBuilder).GetMethod("ApplyConfiguration", BindingFlags.Instance | BindingFlags.Public);            
    // replace GetExecutingAssembly with Assembly where your configurations are if necessary
    foreach (var type in Assembly.GetExecutingAssembly().GetTypes()
        .Where(c => c.IsClass && !c.IsAbstract && !c.ContainsGenericParameters)) 
    {
        // use type.Namespace to filter by namespace if necessary
        foreach (var iface in type.GetInterfaces()) {
            // if type implements interface IEntityTypeConfiguration<SomeEntity>
            if (iface.IsConstructedGenericType && iface.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)) {
                // make concrete ApplyConfiguration<SomeEntity> method
                var applyConcreteMethod = applyGenericMethod.MakeGenericMethod(iface.GenericTypeArguments[0]);
                // and invoke that with fresh instance of your configuration type
                applyConcreteMethod.Invoke(modelBuilder, new object[] {Activator.CreateInstance(type)});
                break;
            }
        }
    }
}
12
Evk

Le travail de Nice de @Evk peut être encapsulé dans une méthode d'extension réutilisable:

 public static class ModelBuilderExtensions
{
    public static void ApplyAllConfigurationsFromCurrentAssembly(this ModelBuilder modelBuilder, Assembly assembly, string configNamespace = "")
    {
        var applyGenericMethods = typeof(ModelBuilder).GetMethods( BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
        var applyGenericApplyConfigurationMethods = applyGenericMethods.Where(m => m.IsGenericMethod && m.Name.Equals("ApplyConfiguration", StringComparison.OrdinalIgnoreCase));
        var applyGenericMethod = applyGenericApplyConfigurationMethods.Where(m=>m.GetParameters().FirstOrDefault().ParameterType.Name== "IEntityTypeConfiguration`1").FirstOrDefault();


        var applicableTypes = Assembly
            .GetTypes()
            .Where(c => c.IsClass && !c.IsAbstract && !c.ContainsGenericParameters);

        if (!string.IsNullOrEmpty(configNamespace))
        {
            applicableTypes = applicableTypes.Where(c => c.Namespace == configNamespace);
        }

        foreach (var type in applicableTypes)
        {
            foreach (var iface in type.GetInterfaces())
            {
                // if type implements interface IEntityTypeConfiguration<SomeEntity>
                if (iface.IsConstructedGenericType && iface.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>))
                {
                    // make concrete ApplyConfiguration<SomeEntity> method
                    var applyConcreteMethod = applyGenericMethod.MakeGenericMethod(iface.GenericTypeArguments[0]);
                    // and invoke that with fresh instance of your configuration type
                    applyConcreteMethod.Invoke(modelBuilder, new object[] { Activator.CreateInstance(type) });
                    Console.WriteLine("applied model " + type.Name);
                    break;
                }
            }
        }
    }
}

Et peut être appelé comme tel:

public class ApiDbContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    public ApiDbContext(DbContextOptions<ApiDbContext> options) : base(options) { }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyAllConfigurationsFromCurrentAssembly("MyRoot.Api.Entities.Configuration");
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
    }
}
8
paul van bladel

La réponse de @Evk est un peu dépassée et ne fonctionne plus, la réponse de @paul van bladel est celle qui fonctionne, même si elle contient des chaînes codées en dur, que je me suis le plus chiché. Si quelqu'un partage mon sentiment, j'aimerais proposer ma solution sous la forme d'une méthode d'extension statique:

public static ModelBuilder ApplyAllConfigurationsFromAssembly(
    this ModelBuilder modelBuilder, Assembly assembly)
{   
    var applyGenericMethod = typeof(ModelBuilder)
        .GetMethods(BindingFlags.Instance | BindingFlags.Public)
        .Single(m => m.Name == nameof(ModelBuilder.ApplyConfiguration)
            && m.GetParameters().Count() == 1
            && m.GetParameters().Single().ParameterType.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>));        
    foreach (var type in Assembly.GetTypes()
        .Where(c => c.IsClass && !c.IsAbstract && !c.ContainsGenericParameters)) 
    {
        foreach (var iface in type.GetInterfaces())
        {
            if (iface.IsConstructedGenericType && iface.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)) 
            {
                var applyConcreteMethod = applyGenericMethod.MakeGenericMethod(iface.GenericTypeArguments[0]);
                applyConcreteMethod.Invoke(modelBuilder, new object[] {Activator.CreateInstance(type)});
                break;
            }
        }
    }
}

Et, si vos classes de configuration sont stockées dans le même assemblage que votre DbContext, vous utilisez cette méthode comme suit:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.ApplyAllConfigurationsFromAssembly(GetType().Assembly);           
    base.OnModelCreating(modelBuilder);
}
5
mr100

En utilisant EF Core 2.2+, c'est devenu beaucoup plus simple:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   Assembly assemblyWithConfigurations = GetType().Assembly; //get whatever Assembly you want
   modelBuilder.ApplyConfigurationsFromAssembly(assemblyWithConfigurations);
}
2
Jan Palas

J'aime beaucoup la réponse fournie par @paul van bladel pour déplacer le code vers une méthode d'extension. J'ai également besoin d'appeler cela depuis d'autres assemblys. J'ai donc créé une énumération et modifié les types applicablesTypes afin qu'ils soient définis différemment. 

IEnumerable<Type> assemblyTypeList;

switch (pAssemblyMethodType)
{
    case AssemblyMethodType.CallingAssembly:
        assemblyTypeList = Assembly.GetCallingAssembly()
            .GetTypes()
            .Where(c => c.IsClass
                && !c.IsAbstract
                && !c.ContainsGenericParameters);
        break;
    case AssemblyMethodType.ExecutingAssembly:
        assemblyTypeList = Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(c => c.IsClass
                && !c.IsAbstract
                && !c.ContainsGenericParameters);
        break;
    default:
        throw new ArgumentOutOfRangeException(nameof(pAssemblyMethodType), pAssemblyMethodType, null);
}
0
Ryan Armstrong