實體框架核心:記錄單個數據庫上下文實例的查詢

c# entity-framework-core logging unit-testing xunit

使用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());
}

但我遇到的問題我懷疑是以下結果(也來自文檔):

您只需要使用單個上下文實例註冊記錄器。註冊後,它將用於同一AppDomain中上下文的所有其他實例。

我在測試中看到的問題表明我的記錄器實現是在多個上下文中共享的(這與我閱讀它們時的文檔一致)。並且因為a)我的測試運行器在並行運行測試而b)我的整個測試套件創建了數百個db上下文 - 它不能很好地工作。

問題/問題:

  • 我想要的是什麼?
  • 即我可以使用僅用於該db上下文實例的db上下文註冊記錄器嗎?
  • 還有其他方法可以完成我想要做的事情嗎?

熱門答案

調用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();
}

通常我使用此功能進行手動測試。為了保持原始上下文類的清潔,使用重寫的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);
    }
}

記錄SQL查詢就足夠了(在將logger傳遞給上下文之前將記錄器附加到loggerFactory )。

第二部分:將日誌傳遞給xUnit輸出和ReSharper測試輸出窗口

我們可以在測試類構造函數中創建一個loggerFactory

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;
}

Test類派生自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);
    }
}

最棘手的部分是實現一個接受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()
        {
        }
    }
}

所需包裹:

<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.1" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />


Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow