C# 访问IEntityTypeConfiguration内部的DI服务<;T>;使用ApplyConfigurationsFromAssembly()程序集扫描时

C# 访问IEntityTypeConfiguration内部的DI服务<;T>;使用ApplyConfigurationsFromAssembly()程序集扫描时,c#,dependency-injection,entity-framework-core,autofac,C#,Dependency Injection,Entity Framework Core,Autofac,我需要访问我的IEntityTypeConfiguration类中的一些DI服务,以便找到一些用户会话信息并执行一些查询过滤 我可以通过“手册”的方式实现这一点,方法如下 // setup config to use injection (everything normal here) public class MyEntityConfig: IEntityTypeConfiguration<MyEntity> { private readon

我需要访问我的IEntityTypeConfiguration类中的一些DI服务,以便找到一些用户会话信息并执行一些查询过滤

我可以通过“手册”的方式实现这一点,方法如下

    // setup config to use injection (everything normal here)
    public class MyEntityConfig: IEntityTypeConfiguration<MyEntity>
    {
        private readonly IService _service;

        public MyEntityConfig(IService service)
        {
            IService = service;
        }


        public void Configure(EntityTypeBuilder<MyEntity> entity)
        {
            // do some stuff to entity here using injected _service
        }
    }

    //use my normal DI (autofac) to inject into my context, then manually inject into config
    public class MyContext: DbContext
    {
        private readonly IService _service;

        public MyContext(DbContextOptions options, IService service) : base(options)
        {
            _service = service;
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //this works no problem
            modelBuilder.ApplyConfiguration(new MyEntityConfig(_service));
        }
    }

但是这样做总是调用IEntityTypeConfiguration的默认ctor,所以我注入的服务都是空的

我曾考虑尝试通过使用反射获取配置,然后自己调用ctor来滚动我自己版本的ApplyConfigurationsFromAssembly,但这似乎令人不快


有什么想法吗?

这就是我在跟随@Cyril的线索并调查来源后想到的。我“借用”了现有的ModelBuilder.ApplyConfigurationsFromAssembly()方法,并重新编写了一个新版本(作为ModelBuilder扩展),它可以接受服务的param列表

        /// <summary>
        /// This extension was built from code ripped out of the EF source.  I re-jigged it to find
        /// both constructors that are empty (like normal) and also those that have services injection
        /// in them and run the appropriate constructor for them and then run the config within them.
        ///
        /// This allows us to write EF configs that have injected services in them.
        /// </summary>
        public static ModelBuilder ApplyConfigurationsFromAssemblyWithServiceInjection(this ModelBuilder modelBuilder, Assembly assembly, params object[] services)
        {
            // get the method 'ApplyConfiguration()' so we can invoke it against instances when we find them
            var applyConfigurationMethod = typeof(ModelBuilder).GetMethods().Single(e => e.Name == "ApplyConfiguration" && e.ContainsGenericParameters &&
                                                                            e.GetParameters().SingleOrDefault()?.ParameterType.GetGenericTypeDefinition() ==
                                                                            typeof(IEntityTypeConfiguration<>));


            // test to find IEntityTypeConfiguration<> classes
            static bool IsEntityTypeConfiguration(Type i) => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>);

            // find all appropriate classes, then create an instance and invoke the configure method on them
            assembly.GetConstructableTypes()
                .ToList()
                .ForEach(t => t.GetInterfaces()
                    .Where(IsEntityTypeConfiguration)
                    .ToList()
                    .ForEach(i =>
                    {
                        {
                            var hasServiceConstructor = t.GetConstructor(services.Select(s => s.GetType()).ToArray()) != null;
                            var hasEmptyConstructor = t.GetConstructor(Type.EmptyTypes) != null;

                            if (hasServiceConstructor)
                            {
                                applyConfigurationMethod
                                    .MakeGenericMethod(i.GenericTypeArguments[0])
                                    .Invoke(modelBuilder, new[] { Activator.CreateInstance(t, services) });
                                Log.Information("Registering EF Config {type} with {count} injected services {services}", t.Name, services.Length, services);
                            }
                            else if (hasEmptyConstructor)
                            {
                                applyConfigurationMethod
                                    .MakeGenericMethod(i.GenericTypeArguments[0])
                                    .Invoke(modelBuilder, new[] { Activator.CreateInstance(t) });
                                Log.Information("Registering EF Config {type} without injected services", t.Name, services.Length);
                            }
                        }
                    })
                );

            return modelBuilder;
        }

        private static IEnumerable<TypeInfo> GetConstructableTypes(this Assembly assembly)
        {
            return assembly.GetLoadableDefinedTypes().Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition);
        }

        private static IEnumerable<TypeInfo> GetLoadableDefinedTypes(this Assembly assembly)
        {
            try
            {
                return assembly.DefinedTypes;
            }
            catch (ReflectionTypeLoadException ex)
            {
                return ex.Types.Where(t => t != null as Type).Select(IntrospectionExtensions.GetTypeInfo);
            }
        }
    }

此实现并不理想,因为所有配置都必须具有无参数构造函数或具有固定服务列表的构造函数(即不能具有ClassA(serviceA)、ClassB(ServiceB);只能具有ClassA(serviceA、ServiceB)、ClassB(serviceA、ServiceB)但这对我的用例来说不是问题,因为这正是我目前需要的


如果我需要一个更灵活的方法,我会让modelbuilder容器意识到这一点,然后使用DI容器在内部进行服务解析,但我现在不需要这样做。

使用
Activator.CreateInstance
。你必须自己进行反射过程。你能分享你迄今为止所做的吗?也许我们可以找到一个更简单的解决方案,添加一个响应,以包含我在查看后运行的内容。
        /// <summary>
        /// This extension was built from code ripped out of the EF source.  I re-jigged it to find
        /// both constructors that are empty (like normal) and also those that have services injection
        /// in them and run the appropriate constructor for them and then run the config within them.
        ///
        /// This allows us to write EF configs that have injected services in them.
        /// </summary>
        public static ModelBuilder ApplyConfigurationsFromAssemblyWithServiceInjection(this ModelBuilder modelBuilder, Assembly assembly, params object[] services)
        {
            // get the method 'ApplyConfiguration()' so we can invoke it against instances when we find them
            var applyConfigurationMethod = typeof(ModelBuilder).GetMethods().Single(e => e.Name == "ApplyConfiguration" && e.ContainsGenericParameters &&
                                                                            e.GetParameters().SingleOrDefault()?.ParameterType.GetGenericTypeDefinition() ==
                                                                            typeof(IEntityTypeConfiguration<>));


            // test to find IEntityTypeConfiguration<> classes
            static bool IsEntityTypeConfiguration(Type i) => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>);

            // find all appropriate classes, then create an instance and invoke the configure method on them
            assembly.GetConstructableTypes()
                .ToList()
                .ForEach(t => t.GetInterfaces()
                    .Where(IsEntityTypeConfiguration)
                    .ToList()
                    .ForEach(i =>
                    {
                        {
                            var hasServiceConstructor = t.GetConstructor(services.Select(s => s.GetType()).ToArray()) != null;
                            var hasEmptyConstructor = t.GetConstructor(Type.EmptyTypes) != null;

                            if (hasServiceConstructor)
                            {
                                applyConfigurationMethod
                                    .MakeGenericMethod(i.GenericTypeArguments[0])
                                    .Invoke(modelBuilder, new[] { Activator.CreateInstance(t, services) });
                                Log.Information("Registering EF Config {type} with {count} injected services {services}", t.Name, services.Length, services);
                            }
                            else if (hasEmptyConstructor)
                            {
                                applyConfigurationMethod
                                    .MakeGenericMethod(i.GenericTypeArguments[0])
                                    .Invoke(modelBuilder, new[] { Activator.CreateInstance(t) });
                                Log.Information("Registering EF Config {type} without injected services", t.Name, services.Length);
                            }
                        }
                    })
                );

            return modelBuilder;
        }

        private static IEnumerable<TypeInfo> GetConstructableTypes(this Assembly assembly)
        {
            return assembly.GetLoadableDefinedTypes().Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition);
        }

        private static IEnumerable<TypeInfo> GetLoadableDefinedTypes(this Assembly assembly)
        {
            try
            {
                return assembly.DefinedTypes;
            }
            catch (ReflectionTypeLoadException ex)
            {
                return ex.Types.Where(t => t != null as Type).Select(IntrospectionExtensions.GetTypeInfo);
            }
        }
    }

modelBuilder.ApplyConfigurationsFromAssemblyWithServiceInjection(typeof(MyContext).Assembly, myService, myOtherService);