«Не удается разрешить службу с заданным уровнем от корневого поставщика» с помощью пользовательского EF Core SeriLog Sink - PullRequest
0 голосов
/ 26 сентября 2018

Я пытаюсь создать собственный приемник SeriLog, который связан с EntityFrameworkCore.Я нашел существующий файл с именем 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();

Есть мысли о том, почему это вызывает ошибку и как это работает?

Обновление

На основании комментариев Эрика я обновил свой код 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 голосов
/ 27 сентября 2018

Для использования Serilog с EF 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();
    

    Исходный код: StartEFEFCore

0 голосов
/ 26 сентября 2018

Когда вы звоните app.ApplicationServices.GetService<EligibilityDbContext>, вы напрямую разрешаете сервис с областью действия из контейнера приложения, который не разрешен.Если вы добавите EligibilityDbContext в качестве параметра в метод Configure, он создаст область и добавит контекст в ваш метод.

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