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

Nul*_*uli 3 c# testing entity-framework

对于某些背景 - 我正在编写一些实体框架功能的抽象,以使那些使用数据访问层的人的工作更容易一些.我是通过代理/包装器来做这件事的,我正处于测试EF成功使用的地步Include().(但不是这与这里的具体问题相关(只是想避免人们建议"不测试EF功能")(我将Include方法委托给EF,这是我实际测试的)))

理想情况下,我想定义一个块(可能是via 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
}
Run Code Online (Sandbox Code Playgroud)

有没有办法实现上述查询计数?


更新:

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

然后只需将一个活动字段添加到GlobalCounter

public static class GlobalCounter
{
    public static int QueryCount = 0;
    public static bool Active = false;
}
Run Code Online (Sandbox Code Playgroud)

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

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

现在,我的测试看起来像这样:

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

Ily*_*kov 5

它可以通过拦截器来实现:

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

GlobalCounter class保留全局变量:

static class GlobalCounter
{
    public static int QueryCount { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

注册拦截器:

public class EntityConfigiration : DbConfiguration
{
    public EntityConfigiration()
    {
        this.AddInterceptor(new EfCommandInterceptor());
    }
}
Run Code Online (Sandbox Code Playgroud)

EntityConfigiration班将被自动注册.您可以在配置文件中注册拦截器.

实体框架教程:拦截

基于代码的配置(EF6以后)

然后使用:

[Test]
public void CalculateQueryCount()
{
    GlobalCounter.QueryCount = 0;

    using (var context = new YourContext())
    {
        //queries
    }

    int actual = GlobalCounter.QueryCount;
}
Run Code Online (Sandbox Code Playgroud)

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