如何在中间件中通过依赖注入使用Entity Framework 6访问数据库?
1. 是否可以通过依赖注入实现该需求?
当然可以,但你踩中了中间件生命周期与EF6 DbContext线程安全的核心冲突点:
默认情况下,ASP.NET Core中间件是单例生命周期(整个应用只创建一个实例)。如果在中间件的构造函数中直接注入Transient或Scoped的服务(比如你的MyLoggingModel或RequestLog),这些服务会被绑定到中间件的单例实例上,导致所有请求复用同一个DbContext实例——而EF6的DbContext本身不是线程安全的,多个请求同时操作它就会触发你遇到的NotSupportedException。
你发现的httpContext.RequestServices.GetService(typeof(MyLoggingModel))之所以有效,是因为RequestServices是当前请求专属的服务范围容器,从这里获取的Transient服务会为每个请求创建新实例,避免了多线程共享DbContext的问题。
更优雅的DI实现方案
推荐通过IServiceProvider在Invoke方法中动态获取请求级服务,或者使用IMiddleware接口(ASP.NET Core 2.1+支持):
方案1:在Invoke方法中注入服务
public class LogMiddleware { private readonly RequestDelegate _next; public LogMiddleware(RequestDelegate next) { _next = next; } // 在Invoke方法中通过IServiceProvider获取请求级服务 public async Task Invoke(HttpContext httpContext, IServiceProvider serviceProvider) { httpContext.Response.OnStarting(async () => { var requestLog = serviceProvider.GetRequiredService<RequestLog>(); await requestLog.WriteRequestToLog(httpContext); }); await _next(httpContext); } }
方案2:使用IMiddleware接口(推荐)
IMiddleware是按请求激活的,能直接注入请求级服务,无需手动从RequestServices获取:
public class LogMiddleware : IMiddleware { private readonly RequestLog _requestLog; // 直接注入RequestLog,框架会为每个请求创建新的中间件实例 public LogMiddleware(RequestLog requestLog) { _requestLog = requestLog; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { context.Response.OnStarting(async () => { await _requestLog.WriteRequestToLog(context); }); await next(context); } }
在Startup中注册:
services.AddTransient<LogMiddleware>();
2. 该不该在中间件中访问数据库?
可以,但需要注意两点:
- 避免阻塞请求:日志写入属于非核心业务,建议用后台队列(比如Hangfire、或者自己实现的内存队列+后台任务)异步处理,不要让DB操作拖慢请求响应速度。
- 异常隔离:日志写入失败不能影响主请求流程,要在日志逻辑中捕获并处理异常,避免崩溃。
3. 使用EF Core是否会有差异?
有明显差异,EF Core的设计更适配ASP.NET Core的DI体系:
- EF Core的DbContext推荐生命周期是
Scoped(每个请求一个实例),框架本身对DbContext的线程安全有更严格的检查和提示。 - 同样,中间件中不能直接注入Scoped的DbContext,但用
IMiddleware接口可以更简洁地解决这个问题,无需手动处理服务范围。 - EF Core的异步API更成熟,配合请求级服务使用时,几乎不会遇到EF6那种奇怪的并发错误(前提是遵循生命周期规则)。
额外提示:主键重复问题
你遇到的主键值重复错误,大概率是因为Log实体的主键未配置为自动生成。在EF6中,需要显式配置:
public class Log { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } // 其他字段... }
或者在Fluent API中配置:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Log>() .Property(l => l.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); }
内容的提问来源于stack exchange,提问作者user4864425

