ASP.NET MVC中混用注入DbContext与Startup.Auth创建的DbContext技术问询
嘿,这个问题我之前也碰到过!核心是要让Owin上下文里的DbContext和你通过DI注入的DbContext共享同一个实例——默认情况下,CreatePerOwinContext会为每个Owin请求单独创建DbContext实例,而DI注入的Scoped实例是另一个,这会导致同一个请求里两个上下文跟踪同一实体,引发状态异常或者保存失败。下面给你具体的实现方案:
步骤1:在DI容器中配置DbContext和UserManager
首先在Startup.cs的ConfigureServices方法里,把ApplicationDbContext和ApplicationUserManager注册为Scoped(每个请求一个实例),确保DI容器统一管理它们的生命周期:
public void ConfigureServices(IServiceCollection services) { // 配置DbContext为Scoped生命周期 services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Scoped); // 注册ApplicationUserManager,依赖DI提供的DbContext services.AddScoped<ApplicationUserManager>(provider => { var userStore = new UserStore<ApplicationUser>(provider.GetRequiredService<ApplicationDbContext>()); var userManager = new ApplicationUserManager(userStore); // 这里可以添加你的UserManager自定义配置,比如密码规则、用户验证逻辑 userManager.UserValidator = new UserValidator<ApplicationUser>(userManager) { AllowOnlyAlphanumericUserNames = false, RequireUniqueEmail = true }; userManager.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = true, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, }; return userManager; }); // 其他MVC相关配置 services.AddControllersWithViews(); }
步骤2:修改ConfigureAuth方法,从DI获取实例
接下来在Startup.Auth.cs的ConfigureAuth方法里,不再用默认的CreatePerOwinContext来创建DbContext和UserManager,而是从DI容器中获取已经注册好的Scoped实例:
public void ConfigureAuth(IAppBuilder app) { // 获取DI的服务提供者 var serviceProvider = app.GetApplicationServices(); // 替换默认的DbContext创建逻辑,使用DI中的实例 app.CreatePerOwinContext(() => serviceProvider.GetRequiredService<ApplicationDbContext>()); // 同样,使用DI中的ApplicationUserManager实例 app.CreatePerOwinContext(() => serviceProvider.GetRequiredService<ApplicationUserManager>()); // 继续保留你的身份验证配置,比如Cookie认证 app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>( validateInterval: TimeSpan.FromMinutes(30), regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) } }); }
为什么这样有效?
通过这种方式,Owin上下文里的ApplicationDbContext和ApplicationUserManager,与你在Controller中通过构造函数注入的ApplicationDbContext,都是同一个请求内的同一个实例,完美解决了上下文冲突的问题。
如果你用的是第三方DI容器(比如Autofac、Unity),思路也是一样的:先在容器中注册DbContext和UserManager为PerRequest生命周期,然后让Owin从容器中解析这些实例即可。
内容的提问来源于stack exchange,提问作者Hooman Bahreini

