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

ASP.NET中ILoggerProvider单例服务与DbContext冲突的解决方法

解决EF Core迁移时自定义日志提供者与DbContext的服务冲突问题

问题本质

你的自定义日志提供者是单例服务,而AppDbContext是作用域服务:

  • 最初直接在日志提供者构造函数注入DbContext,违反了"单例不能依赖作用域服务"的DI规则,导致服务构建失败;
  • 改用IServiceScopeFactory后,在CreateLogger里创建并释放作用域,导致返回的MyLogger持有的DbContext已被释放,且EF Core设计时构建服务时,日志系统与DbContext的依赖链形成循环,引发超时。

解决方案1:设计时禁用自定义日志提供者

EF Core迁移属于设计时操作,此时不需要将日志写入数据库,可以通过环境变量判断环境,仅在运行时注册自定义日志提供者:

修改Program.cs

var isDesignTime = Environment.GetEnvironmentVariable("EF_DESIGN_TIME") == "true";

builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlite(builder.Configuration.GetConnectionString("AppDbConnection"));
});

builder.Services.AddLogging();

// 仅非设计时环境注册自定义日志提供者
if (!isDesignTime)
{
    builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
}

运行迁移时设置环境变量

  • Windows(PowerShell):
    $env:EF_DESIGN_TIME="true"
    Add-Migration InitialCreate
    
  • Linux/macOS:
    export EF_DESIGN_TIME=true
    dotnet ef migrations add InitialCreate
    

解决方案2:延迟获取DbContext,避免作用域提前释放

不在CreateLogger阶段创建作用域,而是在实际写入日志的逻辑中才临时创建作用域获取DbContext,确保DbContext在有效作用域内使用:

修改MyLoggerProvider

public class MyLoggerProvider : ILoggerProvider
{
    private readonly LogLevel minLevel = LogLevel.Information;
    private readonly IServiceScopeFactory scopeFactory;

    public MyLoggerProvider(IServiceScopeFactory scopeFactory)
    {
        this.scopeFactory = scopeFactory;
    }

    public ILogger CreateLogger(string categoryName)
    {
        // 直接传递scopeFactory,不在此创建作用域
        return new MyLogger(minLevel, scopeFactory);
    }

    public void Dispose() {}
}

修改MyLogger

public class MyLogger : ILogger
{
    private readonly LogLevel minLevel;
    private readonly IServiceScopeFactory scopeFactory;

    public MyLogger(LogLevel minLevel, IServiceScopeFactory scopeFactory)
    {
        this.minLevel = minLevel;
        this.scopeFactory = scopeFactory;
    }

    public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;

    public bool IsEnabled(LogLevel logLevel) => logLevel >= minLevel;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel)) return;

        // 仅在写日志时创建作用域,使用完自动释放
        using var scope = scopeFactory.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

        // 执行日志写入逻辑(示例)
        dbContext.LogEntries.Add(new LogEntry
        {
            Level = logLevel.ToString(),
            Message = formatter(state, exception),
            CreatedAt = DateTime.UtcNow
        });
        dbContext.SaveChanges();
    }

    // 空Scope实现,避免null引用
    private class NullScope : IDisposable
    {
        public static NullScope Instance { get; } = new NullScope();
        public void Dispose() {}
    }
}

解决方案3:添加DbContext设计时工厂

让EF Core迁移时直接通过工厂创建DbContext,绕开完整服务提供者的构建流程,彻底避免依赖冲突:

创建AppDbContextDesignTimeFactory.cs

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System.IO;

public class AppDbContextDesignTimeFactory : IDesignTimeDbContextFactory<AppDbContext>
{
    public AppDbContext CreateDbContext(string[] args)
    {
        // 手动加载配置,与运行时保持一致
        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();

        var options = new DbContextOptionsBuilder<AppDbContext>();
        options.UseSqlite(config.GetConnectionString("AppDbConnection"));

        return new AppDbContext(options.Options);
    }
}

组合方案推荐

建议同时使用解决方案2(延迟获取DbContext)和解决方案3(设计时工厂):

  • 运行时保证日志逻辑的正确性和服务生命周期合规;
  • 设计时让迁移流程独立于服务提供者,避免各种依赖问题。

内容的提问来源于stack exchange,提问作者Ibrahim Timimi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 09:45:35