简体   繁体   中英

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. For example, a User Resolver service for audit logging purposes. 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. 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.

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. 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.? Is there a way to inject a service through Startup ConfigureServices?

I ran into a very similar situation. I ended up just injecting it manually in Startup but still resolving the dependencies. I don't love this and wish the services would be injected into the DbContext constructor. 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:

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...

Another approach you could use is to get a static HttpContext like in older versions of MVC. Solution found here: 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM