实体框架核心:记录单个数据库上下文实例的查询 [英] Entity Framework Core: Log queries for a single db context instance
问题描述
使用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.
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
上附加一个合适的记录器(例如Console).
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.
这篇关于实体框架核心:记录单个数据库上下文实例的查询的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!