使用自定义EF Core SeriLog Sink“无法从根提供商解析作用域服务”

时间:2018-09-26 20:42:50

标签: c# entity-framework-core asp.net-core-webapi serilog

我正在尝试创建一个与EntityFrameworkCore绑定的自定义SeriLog接收器。我找到了一个名为Serilog.Sinks.EntityFrameworkCore的现有版本,但它使用了自己的DbContext,因此我需要能够使用现有的DbContext。

因此,我基本上创建了自己的可与DbContext一起使用的代码版本。但是,每次调用Emit方法并尝试加载DbContext时,都会出现以下错误:

Cannot resolve scoped service ... from root provider

我还看到了有关此问题的其他帖子,涉及范围服务和中间件。但是,我不相信自己拥有的就是中间件。

简而言之,这是我代码的核心部分(同样,其中大部分是从前面提到的Git Repo中复制的。)

startup.cs

public void ConfigureServices(IServiceCollection services)
{
   services.AddDbContext<EligibilityDbContext>(opts => opts.UseSqlServer(Configuration.GetConnectionString("EligibilityDbConnection")));
}

public void Configure(IApplicationBuilder app,
                      IHostingEnvironment env, 
                      SystemModelBuilder modelBuilder, 
                      ILoggerFactory loggerFactory)
{
   Log.Logger = new LoggerConfiguration()
       .WriteTo.EntityFrameworkSink(app.ApplicationServices.GetService<EligibilityDbContext>)
.CreateLogger();

loggerFactory.AddSeriLog();
}

EntityFrameworkSinkExtensions.cs

public static class EntityFrameworkSinkExtensions
{
    public static LoggerConfiguration EntityFrameworkSink(
        this LoggerSinkConfiguration loggerConfiguration,
        Func<EligibilityDbContext> dbContextProvider,
        IFormatProvider formatProvider = null) 
    {
        return loggerConfiguration.Sink(new EntityFrameworkSink(dbContextProvider, formatProvider));
    }
}

EntityFrameworkSink.cs

public class EntityFrameworkSink : ILogEventSink
{
    private readonly IFormatProvider _formatProvider;
    private readonly Func<EligibilityDbContext> _dbContextProvider;
    private readonly JsonFormatter _jsonFormatter;
    static readonly object _lock = new object();

    public EntityFrameworkSink(Func<EligibilityDbContext> dbContextProvider, IFormatProvider formatProvider)
    {
        _formatProvider = formatProvider;
        _dbContextProvider = dbContextProvider ?? throw new ArgumentNullException(nameof(dbContextProvider));
        _jsonFormatter = new JsonFormatter(formatProvider: formatProvider);
    }

    public void Emit(LogEvent logEvent)
    {
        lock (_lock)
        {
            if (logEvent == null)
            {
                return;
            }

            try
            {
                var record = ConvertLogEventToLogRecord(logEvent);

                //! This is the line causing the problems!
                DbContext context = _dbContextProvider.Invoke();

                if (context != null)
                {
                    context.Set<LogRecord>().Add(this.ConvertLogEventToLogRecord(logEvent));

                    context.SaveChanges();
                }
            }
            catch(Exception ex)
            {
                // ignored
            }
        }
    }

    private LogRecord ConvertLogEventToLogRecord(LogEvent logEvent)
    {
        if (logEvent == null)
            return null;

        string json = this.ConvertLogEventToJson(logEvent);

        JObject jObject = JObject.Parse(json);
        JToken properties = jObject["Properties"];

        return new LogRecord
        {
            Exception = logEvent.Exception?.ToString(),
            Level = logEvent.Level.ToString(),
            LogEvent = json,
            Message = logEvent.RenderMessage(this._formatProvider),
            MessageTemplate = logEvent.MessageTemplate?.ToString(),
            TimeStamp = logEvent.Timestamp.DateTime.ToUniversalTime(),
            EventId = (int?)properties["EventId"]?["Id"],
            SourceContext = (string)properties["SourceContext"],
            ActionId = (string)properties["ActionId"],
            ActionName = (string)properties["ActionName"],
            RequestId = (string)properties["RequestId"],
            RequestPath = (string)properties["RequestPath"]
        };
    }

    private string ConvertLogEventToJson(LogEvent logEvent)
    {
        if (logEvent == null)
        {
            return null;
        }

        StringBuilder sb = new StringBuilder();
        using (StringWriter writer = new StringWriter(sb))
        {
            this._jsonFormatter.Format(logEvent, writer);
        }

        return sb.ToString();
    }
}

该错误发生在DbContext context = _dbContextProvider.Invoke();行上的EntityFrameworkSink.cs中

关于为什么会引发错误以及如何使其正常工作的任何想法?

更新

根据Eric的评论,我更新了startup.cs代码,如下所示:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, SystemModelBuilder modelBuilder, ILoggerFactory loggerFactory, IServiceProvider provider)
{
   Log.Logger = new LoggerConfiguration()
      .WriteTo.EntityFrameworkSink(provider.GetService<EligibilityDbContext>)
      .CreateLogger();
}

现在我得到了错误:Cannot access a disposed object. Object name: IServiceProvider

要回答的问题

因此,我将涛州的答案标记为答案。但是,不是他所说的,而是他提供的代码实际上提供了答案。我认为EmitBatchAsync并不能真正解决我的问题-但是,我在其他地方遇到了其他一些评论,等等,表明这可能有助于提高性能。

实际解决问题的方法是遵循他的代码示例。在启动时,他正在传递app.ApplicationServices。然后,在实际的Sink实现中,他创建了一个用于解析dbContext实例的范围:

using(var context = service.CreateScope().ServiceProvider.GetRequiredService<EligibilityDbContext>())
{
}

这实际上解决了我遇到的所有错误,并使它按预期的方式工作。谢谢

2 个答案:

答案 0 :(得分:2)

调用app.ApplicationServices.GetService<EligibilityDbContext>时,您是在应用容器中直接解析scoped service,这是不允许的。如果将EligibilityDbContext作为参数添加到Configure方法中,它将生成一个作用域并将上下文注入您的方法中。

public void Configure(IApplicationBuilder app, ..., EligibilityDbContext context)
{
  // ... use context
}

答案 1 :(得分:0)

对于将SerilogEF Core一起使用,您可能需要实现PeriodicBatchingSink而不是ILogEventSink

请按照以下步骤操作:

  1. 安装软件包Serilog.Sinks.PeriodicBatching
  2. EntityFrameworkCoreSinkExtensions

    public static class EntityFrameworkCoreSinkExtensions
    {
    public static LoggerConfiguration EntityFrameworkCoreSink(
              this LoggerSinkConfiguration loggerConfiguration,
              IServiceProvider serviceProvider,
              IFormatProvider formatProvider = null)
    {
        return loggerConfiguration.Sink(new EntityFrameworkCoreSink(serviceProvider, formatProvider, 10 , TimeSpan.FromSeconds(10)));
    }
    }
    
  3. EntityFrameworkCoreSink

       public class EntityFrameworkCoreSink : PeriodicBatchingSink
       {
    private readonly IFormatProvider _formatProvider;
    private readonly IServiceProvider _serviceProvider;
    private readonly JsonFormatter _jsonFormatter;
    static readonly object _lock = new object();
    
    public EntityFrameworkCoreSink(IServiceProvider serviceProvider, IFormatProvider formatProvider, int batchSizeLimit, TimeSpan period):base(batchSizeLimit, period)
    {
        this._formatProvider = formatProvider;
        this._serviceProvider = serviceProvider;
        this._jsonFormatter = new JsonFormatter(formatProvider: formatProvider);
    }
    
    protected override async Task EmitBatchAsync(IEnumerable<LogEvent> events)
    {
        using (var context = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<ApplicationDbContext>())
        {
            if (context != null)
            {
                foreach (var logEvent in events)
                {
                    var log = this.ConvertLogEventToLogRecord(logEvent);
                    await context.AddAsync(log);
                }
                await context.SaveChangesAsync();
            }
        }
    }
    
    private LogRecord ConvertLogEventToLogRecord(LogEvent logEvent)
    {
        if (logEvent == null)
        {
            return null;
        }
    
        string json = this.ConvertLogEventToJson(logEvent);
    
        JObject jObject = JObject.Parse(json);
        JToken properties = jObject["Properties"];
    
        return new LogRecord
        {
            Exception = logEvent.Exception?.ToString(),
            Level = logEvent.Level.ToString(),
            LogEvent = json,
            Message = this._formatProvider == null ? null : logEvent.RenderMessage(this._formatProvider),
            MessageTemplate = logEvent.MessageTemplate?.ToString(),
            TimeStamp = logEvent.Timestamp.DateTime.ToUniversalTime(),
            EventId = (int?)properties["EventId"]?["Id"],
            SourceContext = (string)properties["SourceContext"],
            ActionId = (string)properties["ActionId"],
            ActionName = (string)properties["ActionName"],
            RequestId = (string)properties["RequestId"],
            RequestPath = (string)properties["RequestPath"]
        };
    }
    
    private string ConvertLogEventToJson(LogEvent logEvent)
    {
        if (logEvent == null)
        {
            return null;
        }
    
        StringBuilder sb = new StringBuilder();
        using (StringWriter writer = new StringWriter(sb))
        {
            this._jsonFormatter.Format(logEvent, writer);
        }
    
        return sb.ToString();
    }
    }
    
  4. Startup

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        Log.Logger = new LoggerConfiguration()
                            .WriteTo.EntityFrameworkCoreSink(app.ApplicationServices)
                            .CreateLogger();
        loggerFactory.AddSerilog();
    

    源代码:StartupEFCore