使用ApplyConfigurationsFromAssembly()程序集扫描时访问IEntityTypeConfiguration<;T&>;内部的DI服务 [英] Access DI services inside an IEntityTypeConfiguration<T> when using ApplyConfigurationsFromAssembly() assembly scanning
问题描述
我需要访问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,因此我插入的服务都将是空的。
我曾考虑尝试使用反射获取配置,然后亲自调用ctor的ctor,以尝试滚动我自己的ApplyConfigurationsFromAssembly版本,但这似乎令人不快。
有什么想法吗?
推荐答案
这是我跟随@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),但这对于我的用例来说不是问题,因为这正是我目前需要的。
如果我需要一条更灵活的路径,我将沿着这条路走下去,让ModelBuilder容器感知,然后在内部使用DI容器进行服务解析,但我目前不需要这样做。
这篇关于使用ApplyConfigurationsFromAssembly()程序集扫描时访问IEntityTypeConfiguration<;T&>;内部的DI服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!