Получать доступ к службам DI внутри IEntityTypeConfiguration <T>при использовании сканирования сборки ApplyConfigurationsFromAssembly () - PullRequest
1 голос
/ 31 октября 2019

Мне нужно получить доступ к некоторым службам DI в моих классах IEntityTypeConfiguration, чтобы найти некоторую информацию о сеансе пользователя и выполнить некоторую фильтрацию запросов.

Я могу добиться этого «ручным» способом, выполнив следующее. ..

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

Но при этом всегда вызываетсяctor по умолчанию для IEntityTypeConfiguration <>, поэтому мои внедренные сервисы будут пустыми.

Я подумал о том, чтобы попытаться свернуть мою собственную версию ApplyConfigurationsFromAssembly, используя отражение, чтобы получить конфиги, а затем вызвать самого себя, но это казалось неприятным.

Есть идеи?

1 Ответ

0 голосов
/ 01 ноября 2019

Так вот, что я придумал после того, как последовал примеру @ Сирила и посмотрел на источник. Я «позаимствовал» существующий метод 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, но в данный момент мне это не нужно.

...