"Cannot resolve scoped service from root provider" 使用自定义 EF Core SeriLog 接收器
"Cannot resolve scoped service from root provider" with custom EF Core SeriLog Sink
我正在尝试创建一个与 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();
}
}
错误发生在EntityFrameworkSink.cs行DbContext context = _dbContextProvider.Invoke();
关于为什么会抛出错误以及如何让它工作有什么想法吗?
更新
根据 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>())
{
}
这实际上解决了我遇到的所有错误,并使它按照我预期的方式工作。谢谢
当您调用 app.ApplicationServices.GetService<EligibilityDbContext>
时,您直接从不允许的应用程序容器中解析 scoped service。如果您将 EligibilityDbContext 作为参数添加到 Configure 方法,它将生成一个范围并将上下文注入您的方法。
public void Configure(IApplicationBuilder app, ..., EligibilityDbContext context)
{
// ... use context
}
为了将 Serilog
与 EF Core
一起使用,您可能需要实施 PeriodicBatchingSink
而不是 ILogEventSink
。
按照以下步骤操作:
- 安装包
Serilog.Sinks.PeriodicBatching
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)));
}
}
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();
}
}
Startup
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.EntityFrameworkCoreSink(app.ApplicationServices)
.CreateLogger();
loggerFactory.AddSerilog();
源代码:StartupEFCore
我正在尝试创建一个与 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();
}
}
错误发生在EntityFrameworkSink.cs行DbContext context = _dbContextProvider.Invoke();
关于为什么会抛出错误以及如何让它工作有什么想法吗?
更新
根据 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>())
{
}
这实际上解决了我遇到的所有错误,并使它按照我预期的方式工作。谢谢
当您调用 app.ApplicationServices.GetService<EligibilityDbContext>
时,您直接从不允许的应用程序容器中解析 scoped service。如果您将 EligibilityDbContext 作为参数添加到 Configure 方法,它将生成一个范围并将上下文注入您的方法。
public void Configure(IApplicationBuilder app, ..., EligibilityDbContext context)
{
// ... use context
}
为了将 Serilog
与 EF Core
一起使用,您可能需要实施 PeriodicBatchingSink
而不是 ILogEventSink
。
按照以下步骤操作:
- 安装包
Serilog.Sinks.PeriodicBatching
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))); } }
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(); } }
Startup
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { Log.Logger = new LoggerConfiguration() .WriteTo.EntityFrameworkCoreSink(app.ApplicationServices) .CreateLogger(); loggerFactory.AddSerilog();
源代码:StartupEFCore