如何测试实体框架中随时间推移发生的查询数

本文关键字:时间 查询 何测试 测试 框架 实体 | 更新日期: 2023-09-27 18:35:45

对于一些背景 - 我正在编写一些实体框架功能的抽象,以使使用数据访问层的人员的工作更容易一些。我正在通过代理/包装器执行此操作,并且我想测试EF Include()的成功使用。(但这些都与此处的特定问题无关(只是想避免人们建议"不要测试 EF 功能"(我将 Include 方法委托给 EF,这是我实际测试的内容)))

理想情况下,我想定义一个块(可能通过using),并让该块计算该块内发生的查询数量。

使用一些伪代码,这是我想要的行为:

var user = new User(id);

using(var queryCounter = new QueryCounter()){
  user.Books.SelectMany(b => b.Pages);

  Assert.Equal(2, queryCounter.NumberOfDetectedQueries);
  // the above assert would fail, due to not using the `Include` keyword.
  // as the two queries should be to select books and page ids and 
  // then to actually select the pages
}

有没有办法实现上述查询计数之类的方法?


更新:

感谢@Ilya Chumakov提供查询拦截器的见解。我已经能够通过一个额外的类获取上面示例中的语法:

public class QueryCounter : IDisposable
{
    public int Count => GlobalCounter.QueryCount;
    public QueryCounter()
    {
        GlobalCounter.QueryCount = 0;
        GlobalCounter.Active = true;
    }
    public void Dispose()
    {
        GlobalCounter.Active = false;
        GlobalCounter.QueryCount = 0; //
    }
}

然后只需将活动字段添加到全局计数器

public static class GlobalCounter
{
    public static int QueryCount = 0;
    public static bool Active = false;
}

并修改每个拦截器方法,如下所示:

#if DEBUG
        if (!GlobalCounter.Active) return;
        GlobalCounter.QueryCount++;
        // or whatever output class/method works for you
        Trace.Write("Current Query Count: " + GlobalCounter.QueryCount + " -- ");
        Trace.WriteLine(command.CommandText);
#endif

现在,我的测试如下所示:

        using (var counter = new QueryCounter())
        {
            var pages = user.Books.First().Pages;
            Assert.Equal(1, counter.Count);
        }

如何测试实体框架中随时间推移发生的查询数

可以使用拦截器来实现:

class EfCommandInterceptor : IDbCommandInterceptor
{
    public void NonQueryExecuted(System.Data.Common.DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        GlobalCounter.QueryCount++;
    }
    public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        GlobalCounter.QueryCount++;
    }
    public void ScalarExecuted(System.Data.Common.DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        GlobalCounter.QueryCount++;
    }
    //other methods are empty
}

GlobalCounter类保留全局变量:

static class GlobalCounter
{
    public static int QueryCount { get; set; }
}

注册拦截器:

public class EntityConfigiration : DbConfiguration
{
    public EntityConfigiration()
    {
        this.AddInterceptor(new EfCommandInterceptor());
    }
}

EntityConfigiration类将自动注册。您可以改为在配置文件中注册拦截器。

实体框架教程:拦截

基于代码的配置(EF6 及更高版本)

然后使用:

[Test]
public void CalculateQueryCount()
{
    GlobalCounter.QueryCount = 0;
    using (var context = new YourContext())
    {
        //queries
    }
    int actual = GlobalCounter.QueryCount;
}

EfCommandInterceptor代码是单线程的。对于多线程测试,应该使用锁定(lockInterlocked.Increment等)代替++