If you're like me, Serilog is a must-have in any .NET application because treating logs as data gives us greater insight into the operational activity of the systems.

In ASP.NET Core 2, the most efficient ways to use Serilog is Install Serilog.AspNetCore as it allows you to inject logging as early as possible into the application.

However, there is a caveat with this approach as the Serilog.AspNetCore replaces the ILoggerFactory which can break the integration with EF Core (as discussed at length in this GitHub issue).

Fortunately, this is easy to fix, which is good as EF Core logs out some really interesting things that you need to know about.

The Serilog implementation of SerilogLoggerFactory is registered in the container, so when your configuring the EF Core DbContext inject the ILoggerFactory and pass it into options builder.

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options, ILoggerFactory loggerFactory)
        : base(options)
    {
        _loggerFactory = loggerFactory;
    }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseLoggerFactory(_loggerFactory);
    }

    private readonly ILoggerFactory _loggerFactory;
}

Or alternatively in your Startup.cs if you want to use DbContextPool:

public class Startup
{
    public Startup(IHostingEnvironment environment, IConfiguration configuration)
    {
        _environment = environment;
        _configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContextPool<_NameContext>(options =>
        {
            var provider = services.BuildServiceProvider();
            var loggerFactory = provider.GetService<ILoggerFactory>();

            options.UseSerilog(loggerFactory, throwOnQueryWarnings: !_environment.IsProduction());
        });
    }
}

public static class DbContextOptionsBuilderExtensions
{
    public static DbContextOptionsBuilder UseSerilog(this DbContextOptionsBuilder optionsBuilder, ILoggerFactory loggerFactory, bool throwOnQueryWarnings = false)
    {
        optionsBuilder.UseLoggerFactory(loggerFactory);

        optionsBuilder.ConfigureWarnings(warnings =>
        {
            warnings.Log(RelationalEventId.TransactionError);

            if (!throwOnQueryWarnings)
            {
                warnings.Throw(RelationalEventId.QueryClientEvaluationWarning);
                warnings.Throw(RelationalEventId.QueryPossibleExceptionWithAggregateOperator);
                warnings.Throw(RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning);
            }
            else
            {
                warnings.Log(RelationalEventId.QueryClientEvaluationWarning);
                warnings.Log(RelationalEventId.QueryPossibleExceptionWithAggregateOperator);
                warnings.Log(RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning);
            }
        });

        return optionsBuilder;
    }
}

Now that we have logs, you can start taking advantage of some of the extra EF Core 2 features, like warning when you write a query that evaluates in-memory (read more Client vs. Server Evaluation).

optionsBuilder.ConfigureWarnings(warnings => {
    warnings.Log(RelationalEventId.QueryClientEvaluationWarning);
    warnings.Log(RelationalEventId.QueryPossibleExceptionWithAggregateOperator);
    warnings.Log(RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning);
});