实体框架核心:记录单个数据库上下文实例的查询

cod*_*ape 23 c# logging unit-testing xunit entity-framework-core

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

我之前在Python下使用过SQLAlchemy,在这个堆栈上,这很容易设置.我通常有一个单元测试,它针对一个场景的查询数量,针对内存中的SQLite数据库进行断言.

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

在我的测试设置代码中,我按照文档说的那样做:

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

但我遇到的问题我怀疑是以下结果(也来自文档):

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

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

问题/问题:

  • 我想要的是什么?
  • 即我可以使用仅用于该db上下文实例的db上下文注册记录器吗?
  • 还有其他方法可以完成我想要做的事情吗?

Ily*_*kov 31

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

用法示例:

//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();
}
Run Code Online (Sandbox Code Playgroud)

通常我使用此功能进行手动测试.要保持原始上下文类的清洁,使用重写OnConfiguring方法声明派生的可测试上下文:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

记录SQL查询就足够了(在将记录loggerFactory传递给上下文之前将其附加到记录中).

第二部分:将日志传递给xUnit输出和ReSharper测试输出窗口

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

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;
}
Run Code Online (Sandbox Code Playgroud)

派生测试类BaseTest,允许写入xUnit输出:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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()
        {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所需包裹:

//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();
}
Run Code Online (Sandbox Code Playgroud)


Bil*_*lal 10

很简单,安装此 Nuget 包 => Microsoft.Extensions.Logging.Console(右键单击您的项目 => 管理 Nuget 包 => 然后查找它)(或在此链接https://www.nuget.org/packages /Microsoft.Extensions.Logging.Console/ ) 然后重建项目 // 然后你的数据库上下文必须看起来像这样 =>

    public class Db : DbContext
    {

    public readonly ILoggerFactory MyLoggerFactory;

        public Db()
        {
            MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
        }

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

            optionsBuilder.UseLoggerFactory(MyLoggerFactory);
        }
    }
Run Code Online (Sandbox Code Playgroud)

结果 => 在此处输入图片说明


pwr*_*imo 9

为 EF Core 5.0引入了简单日志记录(多好的名字!)

在配置 DbContext 实例时,可以通过使用 LogTo 从任何类型的应用程序访问 EF Core 日志。此配置通常在 DbContext.OnConfiguring 的覆盖中完成。例如:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine);
Run Code Online (Sandbox Code Playgroud)

或者,可以将 LogTo 作为 AddDbContext 的一部分或在创建 DbContextOptions 实例以传递给 DbContext 构造函数时调用。

写入文件。但我宁愿将某种记录器注入 db 上下文并使用它,而不是在上下文中编写日志记录逻辑。

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();
}
Run Code Online (Sandbox Code Playgroud)