You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在中间件中通过依赖注入使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:42:42