实际上(至少在 EF Core 6 中)您可以在构造实体时使用 DI。解决方案有点 hacky,并且基于 EF Core 功能inject“本机”服务(例如上下文本身)进入实体构造函数:
目前,只能注入 EF Core 已知的服务。正在考虑在未来版本中支持注入应用程序服务。
And AccessorExtensions.GetService<TService>扩展方法似乎支持从 DI 解析服务。
所以基本上只是介绍接受你的演员DbContext
作为实体的参数并调用GetService
并使用服务:
public class MyEntity
{
public MyEntity()
{
}
public MyEntity(SomeContext context)
{
var valueProvider = context.GetService<IValueProvider>();
NotMapped = valueProvider.GetValue();
}
public int Id { get; set; }
[NotMapped]
public string NotMapped { get; set; }
}
// Example value provider:
public interface IValueProvider
{
string GetValue();
}
class ValueProvider : IValueProvider
{
public string GetValue() => "From DI";
}
示例上下文:
public class SomeContext : DbContext
{
public SomeContext(DbContextOptions<SomeContext> options) : base(options)
{
}
public DbSet<MyEntity> Entities { get; set; }
}
例子:
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IValueProvider, ValueProvider>();
serviceCollection.AddDbContext<SomeContext>(builder =>
builder.UseSqlite($"Filename={nameof(SomeContext)}.db"));
var serviceProvider = serviceCollection.BuildServiceProvider();
// init db and add one item
using (var scope = serviceProvider.CreateScope())
{
var someContext = scope.ServiceProvider.GetRequiredService<SomeContext>();
someContext.Database.EnsureDeleted();
someContext.Database.EnsureCreated();
someContext.Add(new MyEntity());
someContext.SaveChanges();
}
// check that value provider is used
using (var scope = serviceProvider.CreateScope())
{
var someContext = scope.ServiceProvider.GetRequiredService<SomeContext>();
var myEntities = someContext.Entities.ToList();
Console.WriteLine(myEntities.First().NotMapped); // prints "From DI"
}
注意var valueProvider = context.GetRequiredService<IValueProvider>();
如果服务未注册,则会抛出异常,因此下一个实现可能会更好:
public MyEntity(SomeContext context)
{
var serviceProvider = context.GetService<IServiceProvider>();
var valueProvider = serviceProvider.GetService<IValueProvider>();
NotMapped = valueProvider?.GetValue() ?? "No Provider";
}
您还可以考虑删除未映射的属性,并使用它和服务创建单独的模型来执行映射。
同样在 EF Core 的第 7 版中new hook对于这种情况应该添加。看到这个github issue.
UPD。 EF Core 7 方法。
EF 7 添加了IMaterializationInterceptor(以及其他一些 - 请参阅docs) 正好可以用于此目标。因此更新后的代码可能如下所示:
不需要 ctor 接受实体中的上下文:
public class MyEntity
{
public int Id { get; set; }
[NotMapped]
public string NotMapped { get; set; }
}
创建一个拦截器并重载它的方法之一(我选择了InitializedInstance
):
class NotMappedValueGeneratingInterceptor : IMaterializationInterceptor
{
public static NotMappedValueGeneratingInterceptor Instance = new ();
public object InitializedInstance(MaterializationInterceptionData materializationData, object entity)
{
if (entity is MyEntity my)
{
var valueProvider = materializationData.Context.GetService<IValueProvider>();
my.NotMapped = valueProvider.GetValue();
}
return entity;
}
}
并使用我们的 DI 方法将拦截器添加到上下文设置中AddDbContext
更改为:
serviceCollection.AddDbContext<SomeContext>(builder =>
builder.UseSqlite($"Filename={nameof(SomeContext)}.db")
.AddInterceptors(NotMappedValueGeneratingInterceptor.Instance));