2016-02-18 97 views
1

我有兩種方法通過其Id找到實體。第一個使用IQueryable對象通過其Id使用lambda表達式查找對象,另一個使用內置的Entity Framework DbSet.Find()方法。我在Visual Studio中編寫了幾個單元測試,爲兩種方法創建速度基準,以確定哪一種更好用。實體框架6 DbSet.Find效率

有趣的是,我寫的方法比在Find中構建的實體框架獲得更好的結果。有誰知道爲什麼?

下面是實體框架方法的代碼:

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); 
    } 
} 
+0

你確定你的基準測試是正確的嗎?如果您先撥打「FindByIdBenchmark」,什麼時間? –

+0

@Pawel在他的回答中說過的所有內容,以及您還需要考慮DB。查詢計劃和結果可以緩存在數據庫中,以查看是否有可能影響結果的任何內容。如果它們不同,您可能必須分析生成的查詢。要做到這一點,你需要使用sql profiler。比較時,不要忘記刷新SQL Server緩存。 – Igor

+0

請參閱我的原始文章以進行編輯以包含我的測試課程。這兩個測試執行的順序不是一個因素,因爲我有控制方法來加載模型。另外,如果您注意到,由於TestInitialize和TestCleanup方法的設置DbContext的方式相同,所以這兩個函數都會受到影響 – AKirby50

回答

1

不知道如何設置測試,甚至不可能嘗試回答問題。是否可以先運行FindBenchmark,時間是否包含引導EF的成本? EF在與第一個查詢無關的實際查詢中執行了一些相當繁重的工作,但是對於延遲初始化,因此您無法將第一個查詢與以下查詢進行比較。另一方面,Find首先在由EF跟蹤的實體中查找實體,而FindById將始終轉到數據庫 - 同樣,如果使用相同的上下文並切換順序,結果可能會完全不同,因爲FindById會將實體和Find不會訪問數據庫。

+0

我的測試類現在已附加到帖子中,我有一個控制方法可以運行,並且可以加載模型。 – AKirby50

+0

我沒有看到加載模型的任何控制方法。我只看到'target = new EdiDataStore();'但它並不例如強制創建模型,加載元數據,生成視圖等。 – Pawel

0

性能測試有幾個錯誤。

  1. 帕維爾在他的回答中是正確的,第一次EF運行時,會產生大量的開銷。第一個EF查詢需要比其他查詢長得多。

  2. 您未測試相同的查詢。查找「查找具有給定主鍵值的實體」(https://msdn.microsoft.com/en-us/library/gg696418(v=vs.113).aspx)。我假設你的IsActive標誌不是主鍵的一部分,所以上面的方法會產生不同的SQL語句。您無法使用不同的SQL語句比較兩種方法的性能。

    例如:dbContext.Set()查找(ID),將產生類似:

    SELECT * FROM foo其中FooID = 123

    在另一方面,dbContext.Set(),其中(。 F => f.IsActive).FirstOrDefault(F => f.FooID == ID)會產生類似:

    SELECT * FROM foo其中FooID = 123 AND IsActive = 1

    這本身和可以產生截然不同的結果,具體取決於SQL使用的索引和哪種類型的exe cution計劃SQL Server決定構建。如果你想比較蘋果,你應該比較:

    dbContext.Set()。Find(123);

    dbContext.Set()。單(F => f.FooID == 123)

  3. 你FindByID正在執行無關代碼。您正在執行Where(...),然後對Where的結果調用AsQueryable(),然後調用FirstOrDefault()。 IQueryable(T).Where()擴展方法已經返回一個IQueryable(T)。之後調用AsQueryable()是不必要的。

如果你想明確地測試不同的方式來檢索相同的數據的性能,那麼你需要確保這些方法正在做同樣的事情。確保運行SQL事件探查器並確保生成的底層SQL相同的最佳方法是。否則,你所測試的是SQL Server是否可以生成兩個不同的數據庫查詢,這些數據庫查詢的執行時間比彼此多或少,這是毫無意義的。

另外,不要在查詢中調用無關的擴展方法。在這種情況下,AsQueryable是無害的,但調用錯誤的擴展方法也可以實現結果集,將每個實體都帶回到內存中,並針對內存中的集合運行另一個查詢。這也會對性能產生巨大影響。

+0

DVK和Pawel。我已經將EF Bootstrapping排除在等式之外,因爲我的代碼片段中有一個名爲「Control()」的測試方法。你會看到,通過首先運行,該方法將有EF Bootstrapping,另外兩個將蘋果與蘋果進行比較。 – AKirby50