简体   繁体   中英

Entity Framework 6 DbSet.Find efficiency

I have two methods of finding an entity by its Id. The first uses an IQueryable object to find the object by its Id using a lambda expression and the other uses the built in Entity Framework DbSet.Find() method. I wrote a couple unit tests in Visual Studio to create speed benchmarks for both methods to determine which one is better to use.

The funny thing is that the method I wrote gets better results than the Entity Framework built in Find. Does anyone know why?

Here is the code for the Entity Framework method:

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

Here is the code for my method:

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

And here is the time it took for each one of these with selecting a few records from the db:

基准测试

Here is my test class:

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

Without knowing how you set up your test it is impossible even to try to answer the question. Can it be that FindBenchmark is run first and the time includes the cost of bootstrapping EF? EF does some quite heavy lifting on the first query that is not related to the actual query but to lazy initialization so you can't really just compare the first query to following queries. Find on the other hand first looks for the entity in the entities tracked by EF while FindById will always go to the database - again if you are using the same context and switch the order the results might be completely different because FindById will bring the entity and Find will not make the trip to the database.

There are several things wrong with the performance test.

  1. Pawel was correct in his answer that, the first time EF runs, there is a great deal of overhead that occurs. The first EF query takes significantly longer than others.

  2. You aren't testing the same query. Find "Finds an entity with the given primary key values" ( https://msdn.microsoft.com/en-us/library/gg696418(v=vs.113).aspx ). I assume your IsActive flag is not part of the primary key, so the methods above will produce different SQL statements. You cannot compare the performance of two methods using different SQL statements.

    example: dbContext.Set().Find(id) would produce something like:

    SELECT * FROM Foo WHERE FooID = 123

    On the other hand, dbContext.Set().Where(f => f.IsActive).FirstOrDefault(f => f.FooID == id) would produce something like:

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

    This in and of itself can produce wildly different results depending on what indexes SQL uses and what kind of execution plan SQL Server decides to build. If you want to compare apples to apples you should be comparing:

    dbContext.Set().Find(123);

    vs.

    dbContext.Set().Single(f => f.FooID == 123)

  3. Your FindByID is executing extraneous code. You are performing a Where(...) and then calling AsQueryable() on the results of the Where, and then calling FirstOrDefault(). The IQueryable(T).Where() extension method already returns an IQueryable(T). Calling AsQueryable() after that is unnecessary.

If you want to explicitly test the performance of different ways to retrieve the same data, then you need to make sure that the methods are doing the exact same thing. The best way to ensure that is to run SQL profiler and make sure that the underlying SQL being generated is the same. Otherwise all you are testing is whether or not SQL Server can generate two different database queries that take more or less time than one-another to execute, which is meaningless.

Also, do not call extraneous extension methods on queries. In this case the AsQueryable was harmless, but calling the wrong extension method can also materialize the result set, bringing every entity back into memory and running another query against the in-memory collection. This can have huge impacts on performance as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM