[英]Dependency Injection of services into an Entity Framework Core database context
I have been attempting to use DI to inject services into my DB Context. 我一直在尝试使用DI将服务注入到我的数据库上下文中。 For example, a User Resolver service for audit logging purposes.
例如,用于审核日志记录的User Resolver服务。 I have some extensions to ChangeTracker that audit created, modified and deleted properties and also an audit logger that logs the changed properties of the entity.
我对ChangeTracker进行了扩展,以审计创建,修改和删除的属性,还具有审计记录器,用于记录实体的已更改属性。 For these to work effectively and seamlessly I need to determine the logged in user.
为了使它们有效且无缝地工作,我需要确定已登录的用户。 In EF Core 1.1, I got this working by simply adding my IUserResolverService to the constructor.
在EF Core 1.1中,我可以通过将IUserResolverService添加到构造函数中来完成此工作。
When I attempted to upgrade to 2.0, I started to run into all sorts of issues as the tooling did not handle the extended constructor. 当我尝试升级到2.0时,由于工具无法处理扩展的构造函数,我开始遇到各种问题。 I eventually removed it and everything started working, but without a reliable way of determining the logged in user.
我最终将其删除,所有内容开始工作,但是没有确定登录用户的可靠方法。
I have tried to find an answer to this on many occasions now, and searching only uncovers a mountain of articles about injecting the context itself into things like controllers. 我现在尝试在许多场合找到答案,并且搜索仅发现了大量关于将上下文本身注入到控制器之类的文章。
Can this be achieved in EF Core without breaking tooling, etc.? 是否可以在EF Core中实现而无需破坏工具等? Is there a way to inject a service through Startup ConfigureServices?
是否有通过Startup ConfigureServices注入服务的方法?
I ran into a very similar situation. 我遇到了非常相似的情况。 I ended up just injecting it manually in Startup but still resolving the dependencies.
我最终只是在Startup中手动注入了它,但仍然解决了依赖性。 I don't love this and wish the services would be injected into the DbContext constructor.
我不喜欢这个,希望将这些服务注入DbContext构造函数中。 Here is my solution below:
这是我下面的解决方案:
public interface IUserContextProvider {
User GetCurrentUser();
}
public class MvcUserContextProvider : IUserContextProvider {
protected IHttpContextAccessor Accessor { get; set; }
public MvcUserContextProvider(IHttpContextAccessor accessor) {
Accessor = accessor;
}
public User GetCurrentUser() {
// Probably need some error handling and Special case pattern when
// context is null.
return (User) Accessor.HttpContext.User.Identity;
}
}
public interface IEntityFrameworkOverrides {
void EntityInitializing(ModelBuilder modelBuilder, IMutableEntityType entity);
void EntityChanged(EntityEntry entity);
}
public abstract class UserAwareOverride : IEntityFrameworkOverrides {
protected IUserContextProvider UserProvider { get; private set; }
protected UserAwareOverride(IUserContextProvider userProvider)
{
UserProvider = userProvider;
}
public abstract void EntityInitializing(ModelBuilder modelBuilder, IMutableEntityType entity);
public abstract void EntityChanged(EntityEntry entity);
}
public class ApplicationContext : IdentityDbContext<User> {
private static bool ConfigurOverridesSet = false;
protected static readonly List<IEntityFrameworkOverrides> EFOverrides = new List<IEntityFrameworkOverrides>();
public static void ConfigureOverrides(IEnumerable<IEntityFrameworkOverrides> overrides) {
if (ConfigurOverridesSet) {
throw new NotSupportedException("Cannot set overrides on SplitFamContext More than once.");
}
EFOverrides.AddRange(overrides);
ConfigurOverridesSet = true;
}
public ApplicationContext(DbContextOptions<ApplicationContext> options)
:base(options) { }
}
And then in Startup I just did this: 然后在Startup中,我只是这样做了:
public IServiceProvider ConfigureServices(IServiceCollection services) {
services.AddMvc();
var builder = new ContainerBuilder();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IUserContextProvider, MvcUserContextProvider>();
services.AddDbContext<ApplicationContext>(options => {
options.UseNpgsql(
Configuration.GetConnectionString("ApplicationContext"));
});
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<ApplicationContext>()
.AddDefaultTokenProviders();
builder.Populate(services);
this.ApplicationContainer = builder.Build();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(this.ApplicationContainer);
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IApplicationLifetime appLifetime) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMvc();
var userContexProvider = app.ApplicationServices.GetService<IUserContextProvider>();
ApplicationContext.ConfigureOverrides(new List<IEntityFrameworkOverrides>(){
new SoftDeleteOverrides(userContexProvider),
new TrackCreatedOverride(userContexProvider),
new TrackModifiedOverride(userContexProvider)
});
appLifetime.ApplicationStopped.Register(() => {
this.ApplicationContainer.Dispose();
});
}
Probably needs some tweaks and I don't love it but it could be a work around until you can inject services into DbContext... 可能需要一些调整,但我不喜欢它,但是在您可以将服务注入DbContext之前,它可能是一种解决方法。
Another approach you could use is to get a static HttpContext like in older versions of MVC. 您可以使用的另一种方法是像在旧版本的MVC中那样获取静态HttpContext。 Solution found here: https://www.strathweb.com/2016/12/accessing-httpcontext-outside-of-framework-components-in-asp-net-core/
在此处找到解决方案: https : //www.strathweb.com/2016/12/accessing-httpcontext-outside-of-framework-components-in-asp-net-core/
I still haven't tried the Thread.CurrentPrincipal.Identity yet either nor do I know of any problems with the same. 我仍然还没有尝试过Thread.CurrentPrincipal.Identity,也不知道有什么问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.