Search code examples
c#unit-testingloggingentity-framework-corexunit

Entity Framework Core: Log queries for a single db context instance


Using EF Core (or any ORM for that matter) I want to keep track of the number of queries the ORM makes to the database during some operation in my software.

I've used SQLAlchemy under Python earlier, and on that stack this is faily easy to set up. I typically have unit tests that assert on the number of queries made for a scenario, against an in-memory SQLite database.

Now I want to do the same thing using EF Core, and have looked at the Logging documentation.

In my test setup code I do as the documentation says:

using (var db = new BloggingContext())
{
    var serviceProvider = db.GetInfrastructure<IServiceProvider>();
    var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
    loggerFactory.AddProvider(new MyLoggerProvider());
}

But I run into problems that I suspect are the results of the following (also from the docs):

You only need to register the logger with a single context instance. Once you have registered it, it will be used for all other instances of the context in the same AppDomain.

The problems I see in my tests indicates that my logger implementation is shared across multiple contexts (this is in accordance with the docs as I read them). And since a) my test runner runs tests in parallell and b) my entire test suite creates hundreds of db contexts - it does not work very well.

Question/issues:

  • Is what I want possible?
  • I.e. can I register a logger with a db context that is only used for that db context instance?
  • Are there other ways to accomplish what I am trying to do?

Solution

  • For EF Core 5.0 Simple Logging (what a name!) was introduced

    EF Core logs can be accessed from any type of application through the use of LogTo when configuring a DbContext instance. This configuration is commonly done in an override of DbContext.OnConfiguring. For example:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.LogTo(Console.WriteLine);
    

    Alternately, LogTo can be called as part of AddDbContext or when creating a DbContextOptions instance to pass to the DbContext constructor.

    Writing to a file. But I'd rather inject some kind of logger into db context and use it instead of writing logging logic inside of context.

    private readonly StreamWriter _logStream = new StreamWriter("mylog.txt", append: true);
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.LogTo(_logStream.WriteLine);
    
    public override void Dispose()
    {
        base.Dispose();
        _logStream.Dispose();
    }
    
    public override async ValueTask DisposeAsync()
    {
        await base.DisposeAsync();
        await _logStream.DisposeAsync();
    }