Linq to Entities Expression / Func之间的性能差异

时间:2014-05-12 01:12:21

标签: c# performance linq entity-framework

我只是测试一个简单的查询,我以不同的方式访问,但每个查询的速度最多可以变化2秒。我希望有人能澄清为什么会这样。我的项目处于早期阶段,所以我想我确保在它变得太大之前就做好了。

不可否认,我的测试风格并不完美,但我认为它已经足够好了。

我使用通用存储库和UnitofWork,并在此while语句中点击了DB(本地计算机上的sqlexpress)10,000次。该表只有64条记录。测试在发布模式下运行。

[TestMethod]
public void MyTestMethod()
{
    using (var u = new UnitOfWork())
    {
        TestA(u);
        TestB(u);
    }
}

TestA(Func):

public void TestA(UnitOfWork u)
{
    Stopwatch s = Stopwatch.StartNew();
    s.Start();
    var x = 0;
    var repo = u.Repository<MyEntity>();
    var code = "ABCD".First().ToString();
    while (x < 10000)
    {
        var testCase = repo.Single(w => w.Code == code && w.CodeOrder == 0).Name;
        x++;
    }
    s.Stop();

    Console.WriteLine("TESTA: " + s.Elapsed);
}

TestB(表达式):

public void TestB(UnitOfWork u)
{
    Stopwatch s = Stopwatch.StartNew();
    s.Start();
    var x = 0;
    var repo = u.Repository<MyEntity>();
    var code = "ABCD".First().ToString();
    while (x < 10000)
    {
        var testCase = repo.First(w => w.Code == code && w.CodeOrder == 0).Name;
        x++;
    }
    s.Stop();
    Console.WriteLine("TESTB: " + s.Elapsed);
}

即使我正在使用First()Single()来电,但它们并不是内置的LINQ调用。他们是我的存储库的一部分。

First()表达式(IQueryable)

public TEntity Single(Func<TEntity, bool> predicate)
{
    return dbSet.FirstOrDefault(predicate);
}

Single() func(IEnumerable)

public TEntity First(Expression<Func<TEntity, bool>> predicate)
{
    return dbSet.FirstOrDefault(predicate);
}

输出:

Test Name: MyTestMethod
Test Outcome: Passed

Result StandardOutput:  

TESTA: 00:00:02.4798818
TESTB: 00:00:03.4212112

3 个答案:

答案 0 :(得分:2)

带有Expression<Func<...>>参数的

First()IQueryable<T>上的扩展方法,供查询提供程序使用,如LINQ to Entities。您提供的表达式树将转换为正确的SQL查询,该查询将发送到DB,并且只将必要的行返回给您的应用程序。

具有Func<...>参数的

First()IEnumerable<T>上的扩展方法,由LINQ to Objects使用,这意味着数据库中的所有记录都将被提取到应用程序内存中,然后是元素将搜索为内存中查询,实现为线性搜索。

您绝对应该使用IQueryable<T>中的那个,因为它会更有效(因为数据库已经过优化以执行查询)。

答案 1 :(得分:1)

我将列出一些您可能想要尝试的测试,以帮助您缩小操作之间的差异。

检查实际的SQL代码

打开查询的调试日志或在SSE日志上进行检查。这很重要,因为EF引擎应该优化语句,你可以看到发送给数据库的真正内容。

正如你所说,First操作应该更快,因为有优化的SQL运算符。 Single应该更慢,因为它必须验证所有值,并且会根据行数进行缩放。

使用数据库上的真实SQL进行参考测试

拥有真正的SQL后,您还可以直接检查数据库所用时间的差异。在DB上执行相同的C#测试,也可以使用Sotred Procedure,看看会发生什么。

尝试使用内置LINQ进行比较

我不知道你是否已经为测试做过,但尝试使用原生LINQ进行比较。

我在这里使用LINQ做了很多测试,你提出的两个语句之间没有区别,所以它实际上可能是表达式。 (我使用SS CE btw)。

另外,只是为了说出来,重新为重型操作中涉及的列创建索引;) EF 6.1现在内置了此功能。

  [Index]
  public String MyProperty{ get; set; }

让我知道它是否有用。

答案 2 :(得分:1)

这不是答案,只是试图确保测试结果更可靠。

尝试编写这样的测试:

public long TestA()
{
    using (var u = new UnitOfWork())
    {
        var s = Stopwatch.StartNew();
        var x = 0;
        var repo = u.Repository<MyEntity>();
        var code = "ABCD".First().ToString();
        while (x < 10000)
        {
            var testCase = repo.Single(w => w.Code == code && w.CodeOrder == 0).Name;
            x++;
        }
        s.Stop();
        return s.ElapsedMilliseconds;
    }
}

(显然TestB只是一个小变种。)

然后您的测试方法变为:

[TestMethod]
public void MyTestMethod()
{
    var dummyA = TestA();
    var dummyB = TestB();

    var realA = 0L;
    var realB = 0L;
    for (var i = 0; i < 10; i++)
    {
        realA += TestA();
        realB += TestB();
    }

    Console.WriteLine("TESTA: " + realA.ToString());
    Console.WriteLine("TESTB: " + realA.ToString());
}

现在您的结果可能会更准确。现在让我们知道时间安排。


现在尝试更改您的测试:

public int TestA()
{
    var gc0 = GC.CollectionCount(0);
    using (var u = new UnitOfWork())
    {
        var s = Stopwatch.StartNew();
        var x = 0;
        var repo = u.Repository<MyEntity>();
        var code = "ABCD".First().ToString();
        while (x < 10000)
        {
            var testCase = repo.Single(w => w.Code == code && w.CodeOrder == 0).Name;
            x++;
        }
        s.Stop();
    }
    return GC.CollectionCount(0) - gc0;
}

这应该确定正在执行多少代0垃圾收集。这可能表明性能问题与您的测试有关,而与SQL无关。