Массовый регистр IEntityTypeConfiguration <> Сущность Framework core

Хорошо, поэтому я использую инфраструктуру сущностей с ядром и чистым ядром и первым переносом кода. Это не проблема как таковая, я просто задавался вопросом, нашел ли кто-нибудь лучший способ сделать это.

В настоящее время у меня много конфигураций типа объектов, таких как

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

      // more options here
   }
}

и я зарегистрирую их в своем dbcontext так

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

  modelBuilder.ApplyConfiguration(new ExampleEntityConfiguration());

  // lot more configurations here
}

Кто-нибудь сталкивался или знал способ зарегистрировать все интерфейсы IEntityTypeConfiguration?

Это просто похоже на много повторяющийся код, который можно решить, получив список конфигураций, перейдя через них и применив их в контексте. Я просто не знаю, с чего начать с получения списка классов IEntityTypeConfiguration, которые существуют в определенном пространстве имен.

Любая помощь/предложения будут замечательными.

Ответ 1

Это можно сделать с отражением следующим образом:

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

Ответ 2

Хорошая работа из @Evk kan будет дополнительно инкапсулирована в метод многократного использования:

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

И можно назвать таковым:

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

Ответ 3

Ответ от @Evk немного устарел и больше не работает, ответ от @paul van bladel - рабочий, хотя он содержит жестко закодированные строки, которые я очень ненавидел. Если кто-то разделяет мои чувства, я хотел бы предложить свое решение в форме создания статического метода расширения:

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

И, если ваши классы конфигурации хранятся в той же сборке, что и ваш DbContext, вы используете этот метод следующим образом:

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

Ответ 4

Используя EF Core 2. 2+, это стало намного проще:

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

Ответ 5

Мне очень нравится ответ @paul van bladel, предназначенный для перемещения кода в метод расширения. Мне также нужно вызвать это из других сборок, поэтому я создал перечисление и изменил применимые типы, которые будут установлены по-разному.

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