Entity Framework Core:单个数据库上下文实例的日志查询 [英] Entity Framework Core: Log queries for a single db context instance

查看:22
本文介绍了Entity Framework Core:单个数据库上下文实例的日志查询的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用 EF Core(或任何与此相关的 ORM)我想跟踪 ORM 在我的软件的某些操作期间对数据库进行的查询次数.

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.

我之前在 Python 下使用过 SQLAlchemy,但在该堆栈上设置起来并不容易.我通常会针对内存中的 SQLite 数据库进行单元测试,以断言针对某个场景所做的查询数量.

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.

现在我想使用 EF Core 做同样的事情,并查看了 记录文档.

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):

您只需要使用单个上下文实例注册记录器.注册后,它将用于所有其他实例同一个 AppDomain 中的上下文.

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.

我在测试中看到的问题表明我的记录器实现是跨多个上下文共享的(这与我阅读的文档一致).因为 a) 我的测试运行器并行运行测试 b) 我的整个测试套件创建了数百个 db 上下文 - 它不能很好地工作.

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.

问题/问题:

  • 我想要的可能吗?
  • 即我可以使用仅用于该数据库上下文实例的数据库上下文注册记录器吗?
  • 还有其他方法可以完成我正在尝试做的事情吗?

推荐答案

调用 DbContextOptionsBuilder.UseLoggerFactory(loggerFactory) 方法来记录特定上下文实例的所有 SQL 输出.您可以在上下文的构造函数中注入记录器工厂.

Call DbContextOptionsBuilder.UseLoggerFactory(loggerFactory) method to log all SQL output of a particular context instance. You could inject a logger factory in the context's constructor.

这是一个用法示例:

//this context writes SQL to any logs and to ReSharper test output window
using (var context = new TestContext(_loggerFactory))
{
    var customers = context.Customer.ToList();
}

//this context doesn't
using (var context = new TestContext())
{
    var products = context.Product.ToList();
}

一般情况下,我会使用此功能进行手动测试.为了保持原始上下文类的清洁,使用重写的 OnConfiguring 方法声明派生的可测试上下文:

Generally, I use this feature for manual testing. To keep the original context class clean, a derived testable context is declared with overridden OnConfiguring method:

public class TestContext : FooContext
{
    private readonly ILoggerFactory _loggerFactory;

    public TestContext() { }

    public TestContext(ILoggerFactory loggerFactory)
    {
        _loggerFactory = loggerFactory;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);

        optionsBuilder.UseLoggerFactory(_loggerFactory);
    }
}

记录 SQL 查询就足够了.在将它传递给上下文之前,不要忘记将合适的记录器(如控制台)附加到 loggerFactory.

It's enough to log SQL queries. Don't forget to attach a suitable logger (like Console) to loggerFactory before you pass it to context.

我们可以在测试类构造函数中创建一个loggerFactory:

We can create a loggerFactory in a test class constructor:

public class TestContext_SmokeTests : BaseTest
{
    public TestContext_SmokeTests(ITestOutputHelper output)
        : base(output)
    {
        var serviceProvider = new ServiceCollection().AddLogging().BuildServiceProvider();

        _loggerFactory = serviceProvider.GetService<ILoggerFactory>();

        _loggerFactory.AddProvider(new XUnitLoggerProvider(this));
    }

    private readonly ILoggerFactory _loggerFactory;
}

测试类派生自BaseTest,支持写入xUnit输出:

The test class is derived from BaseTest which supports the writing to xUnit output:

public interface IWriter
{
    void WriteLine(string str);
}

public class BaseTest : IWriter
{
    public ITestOutputHelper Output { get; }

    public BaseTest(ITestOutputHelper output)
    {
        Output = output;
    }

    public void WriteLine(string str)
    {
        Output.WriteLine(str ?? Environment.NewLine);
    }
}

最棘手的部分是实现一个接受 IWriter 作为参数的日志提供程序:

The most tricky part is to implement a logging provider accepting IWriter as a parameter:

public class XUnitLoggerProvider : ILoggerProvider
{
    public IWriter Writer { get; private set; }

    public XUnitLoggerProvider(IWriter writer)
    {
        Writer = writer;
    }
    public void Dispose()
    {
    }

    public ILogger CreateLogger(string categoryName)
    {
        return new XUnitLogger(Writer);
    }

    public class XUnitLogger : ILogger
    {
        public IWriter Writer { get; }

        public XUnitLogger(IWriter writer)
        {
            Writer = writer;
            Name = nameof(XUnitLogger);
        }

        public string Name { get; set; }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
            Func<TState, Exception, string> formatter)
        {
            if (!this.IsEnabled(logLevel))
                return;

            if (formatter == null)
                throw new ArgumentNullException(nameof(formatter));

            string message = formatter(state, exception);
            if (string.IsNullOrEmpty(message) && exception == null)
                return;

            string line = $"{logLevel}: {this.Name}: {message}";

            Writer.WriteLine(line);

            if (exception != null)
                Writer.WriteLine(exception.ToString());
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return true;
        }

        public IDisposable BeginScope<TState>(TState state)
        {
            return new XUnitScope();
        }
    }

    public class XUnitScope : IDisposable
    {
        public void Dispose()
        {
        }
    }
}

我们已经完成了!所有 SQL 日志都将显示在 Rider/Resharper 测试输出窗口中.

We've done here! All the SQL logs will be shown in Rider/Resharper test output window.

这篇关于Entity Framework Core:单个数据库上下文实例的日志查询的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆