[访问 IEntityTypeConfiguration内部的DI服务 当使用ApplyConfigurationsFromAssembly()程序集扫描时

问题描述 投票:1回答:1

我需要在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));
        }
    }

我在最后一部分要做的是使用程序集扫描通过...导入我的配置...

 \\modelBuilder.ApplyConfiguration(new MyEntityConfig(_service));
 modelBuilder.ApplyConfigurationsFromAssembly(typeof(MyContext).Assembly);

但是以这种方式这样做总是为IEntityTypeConfiguration <>调用默认的ctor,因此我注入的服务将全部为空。

[我考虑过尝试使用反射来获取自己的版本的ApplyConfigurationsFromAssembly,以获取配置,然后调用ctor的本人,但这似乎不愉快。

有什么想法吗?

c# dependency-injection entity-framework-core autofac
1个回答
0
投票

所以这就是我跟随@Cyril的线索并调查了源代码之后得出的结果。我“借用”了现有的ModelBuilder.ApplyConfigurationsFromAssembly()方法,并重新编写了可以获取服务参数列表的新版本(作为模型构建器扩展)。

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

然后在我的OnModelCreating()中,我只是叫扩展名...

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

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

如果需要一条更灵活的路径,我将沿用使模型构建器容器具有感知能力,然后使用DI容器在内部进行服务解析的方法,但是目前不需要。

© www.soinside.com 2019 - 2024. All rights reserved.