实体框架6 DbSet.Find效率

时间:2016-02-18 21:03:58

标签: c# entity-framework

我有两种方法可以通过Id查找实体。第一个使用IQueryable对象通过其Id使用lambda表达式查找对象,另一个使用内置的Entity Framework DbSet.Find()方法。我在Visual Studio中编写了几个单元测试,为两种方法创建速度基准,以确定哪种方法更好用。

有趣的是,我编写的方法比在Find中构建的实体框架获得更好的结果。有谁知道为什么?

以下是Entity Framework方法的代码:

public virtual T Find<T>(int id)
{
    return dbContext.Set<T>().Find(id);
}

以下是我方法的代码:

public virtual T FindById<T>(int id)
{
    return dbContext.Set<T>().Where(x => x.IsActive).AsQueryable().FirstOrDefault(x => x.Id == id);
}

以下是从db中选择几条记录所花费的时间:

Benchmark Tests

这是我的测试类:

[TestClass]
public class EntityBenchmarks
{
    EdiDataStore target;

    [TestInitialize]
    public void Start()
    {
        target = new EdiDataStore();
    }

    [TestCleanup]
    public void Cleanup()
    {
        target.Dispose();
    }

    //this method is only here because i want to make sure that the 
    //database context is loaded into memory so that we can compare 
    //Find and FindById on an even scale.  Without this method, the first 
    //time benchmark that runs is hit with the overhead of loading the model.
    [TestMethod]
    public void Control()
    {
        var entities = target.GetAgencies();
    }

    [TestMethod]
    public void FindBenchmark()
    {
        bool isSuccess = true;

        for (int i = 9177; i <= 9187; i++)
        {
            var entity = target.Find<Spot>(i);
            isSuccess = isSuccess && entity != null;
        }

        Assert.IsTrue(isSuccess);
    }

    [TestMethod]
    public void FindByIdBenchmark()
    {
        bool isSuccess = true;

        for (int i = 9177; i <= 9187; i++)
        {
            var entity = target.FindById<Spot>(i);
            isSuccess = isSuccess && entity != null;
        }

        Assert.IsTrue(isSuccess);
    }
}

2 个答案:

答案 0 :(得分:1)

如果不知道如何设置测试,甚至无法尝试回答问题。可能是FindBenchmark首先运行,时间包括引导EF的成本吗? EF对第一个与实际查询无关但与惰性初始化相关的查询做了一些非常繁重的工作,因此您无法将第一个查询与下面的查询进行比较。另一方面,Find首先在EF跟踪的实体中查找实体,而FindById将始终转到数据库 - 如果您使用相同的上下文并切换结果可能完全是不同,因为FindById将带来实体,而Find将不会访问数据库。

答案 1 :(得分:0)

性能测试有几个问题。

  1. Pawel的答案是正确的,即EF第一次运行时,会产生大量的开销。第一个EF查询比其他查询花费的时间要长得多。

  2. 您没有测试相同的查询。查找“查找具有给定主键值的实体”(https://msdn.microsoft.com/en-us/library/gg696418(v=vs.113).aspx)。我假设你的IsActive标志不是主键的一部分,所以上面的方法将产生不同的SQL语句。您无法使用不同的SQL语句来比较两种方法的性能。

    示例:dbContext.Set()。Find(id)将生成如下内容:

    SELECT * FROM Foo WHERE FooID = 123

    另一方面,dbContext.Set()。Where(f =&gt; f.IsActive).FirstOrDefault(f =&gt; f.FooID == id)会产生如下内容:

    SELECT * FROM Foo WHERE FooID = 123 AND IsActive = 1

    这本身就会产生截然不同的结果,具体取决于SQL使用的索引以及SQL Server决定构建的执行计划类型。如果你想比较苹果和苹果,你应该比较:

    dbContext.Set()查找(123);

    VS

    dbContext.Set()。Single(f =&gt; f.FooID == 123)

  3. 您的FindByID正在执行无关代码。您正在执行Where(...),然后在Where的结果上调用AsQueryable(),然后调用FirstOrDefault()。 IQueryable(T).Where()扩展方法已经返回IQueryable(T)。之后调用AsQueryable()是不必要的。

  4. 如果要显式测试检索相同数据的不同方法的性能,那么您需要确保方法完全相同。确保这一点的最佳方法是运行SQL事件探查器并确保生成的基础SQL是相同的。否则,您正在测试的是SQL Server是否可以生成两个不同的数据库查询,这些查询比另一个执行时间更多或更少,这是没有意义的。

    此外,不要在查询上调用无关的扩展方法。在这种情况下,AsQueryable是无害的,但调用错误的扩展方法也可以实现结果集,将每个实体带回内存并针对内存中的集合运行另一个查询。这也会对性能产生巨大影响。