2012-03-20 51 views
6

我注意到EF的DbSet.Add()很慢。一個小Googling打開了一個SO答案,承諾高達180X的性能提升:爲POCO實現IEquatable

https://stackoverflow.com/a/7052504/141172

不過,我不明白究竟如何實現IEquatable<T>作爲答案建議。

According to MSDN,如果我執行IEquatable<T>,我也應該覆蓋Equals()GetHashCode()

與許多POCO的一樣,我的對象是mutable。在提交到數據庫之前(SaveChanges()),新對象的ID爲0. 之後對象已保存,Id用作實現IEquatable,Equals()和GetHashCode()的理想基礎。

是不明智的包括在一個哈希碼任何可變屬性,並且由於according to MSDN

如果兩個對象的比較結果爲相等,對於每個 對象GetHashCode方法必須返回相同的值

我是否應該將IEquatable<T>作爲按屬性進行比較(例如this.FirstName == other.FirstName)並且不覆蓋Equals()和GetHashCode()?

鑑於我的POCO在EntityFramework上下文中使用,是否應該特別注意Id字段?

+0

爲什麼你的對象的ID爲0?爲什麼不像JomTois在他的代碼示例中所示,直接將一個Guid分配給ID字段/屬性。這是您提到自己的IEquatable的理想基礎。 TomTom還指出了爲每個客戶端分配一個id Range的方法,或者使用-1,-2和-3作爲臨時id,這些「解決方案」看起來並不複雜。施工後,您會生成一個新的Guid並開始營業。或者我錯過了什麼? – 2012-11-17 19:35:43

+0

@YoupTube:整數不能毫無價值。另外,整數是32位,而GUID是128位。這意味着SQL Server可以使用Integer(在執行連接時性能的關鍵)將4倍的內存ID放在內存中。 – 2012-11-19 18:07:53

+0

啊,我明白了。但是,您是否有大量的數據,流量,連接並需要超高性能?這總是一種折衷......我知道。但使用guid應該不是什麼大問題。然後,您可以忘記由於使用整數而導致的所有複雜問題。 – 2012-11-20 11:26:01

回答

1

與許多POCO的,我的對象是可變

但tehy不應該是可變的是主鍵字段。根據定義,或者你後來處於疼痛數據庫的世界。

僅在主鍵的字段上生成HashCode。錯誤 - IFF參與對象具有相同的散列碼

BZZZ

equals()方法必須返回true。

哈希碼是雙倍的。 2個對象可能有不同的值和smae hashcode。 hsahsode是一個int(32位)。一個字符串可以是2GB長。您無法將每個可能的字符串映射到單獨的哈希碼。

如果兩個對象具有相同的哈希碼,它們可能是不同的。如果兩個對象相同,則它們不能具有不同的哈希碼。

你從哪裏得到這樣的想法,Equals必須爲具有相同散列碼的對象返回true?

另外,PCO與否,映射到數據庫並用於關係的對象必須具有穩定的主鍵(可用於運行哈希碼計算)。沒有這個STIL的對象應該有主鍵(按照SQL Server的要求),使用序列/人工主鍵在這裏工作。再次使用它來運行HashCode計算。

+0

你是對的,Equals和GetHashCode之間的關係是另一種解決方法「如果兩個對象相等,每個對象的GetHashCode方法必須返回相同的值」http://msdn.microsoft.com/en-us /library/system.object.gethashcode.aspx。關鍵點:一旦一個對象被插入到數據庫中,身份和平等就很簡單。在我的情況下,我正在實例化新的對象,並且還沒有調用SaveChanges(),因此所有的Id均爲0.我將根據您的意見修改我的問題,但是我沒有看到支持新對象的解決方案沒有分配PK。 – 2012-03-20 07:07:12

+1

使用客戶端生成的主鍵。 GUID#s,您在客戶端生成的序列。否則,通常的參考機制是痛苦的。或者 - 去自己的ID。一年前,我自己的ORM使用了負數(-1,-2,-3)作爲臨時密鑰,取代了插入。 G在提交之後無論如何都不合法地重用哈希碼(對象需要刷新);)問題解決了。 – TomTom 2012-03-20 07:10:17

+0

+1提到相同的哈希碼不會自動錶示您的對象是相同的。 – VVS 2012-03-20 07:10:33

0

第一件事,第一:對不起我的跛腳英語:)

由於TomTom的說,只是因爲他們還沒有收到PK /編號他們不應該是可變的...

在我們的EF:CF系統,我們使用生成的負ID(在基類ctor中分配,或者如果您使用ProxyTracking,則在ObjectMaterialized事件中)爲每個新的POCO使用。它非常簡單的想法:

public static class IdKeeper 
{ 
    private static int m_Current = int.MinValue; 
    private static Next() 
    { 
    return ++m_Current; 
    } 
} 

MINVALUE和incremen應該是重要的,因爲EF將由他們PK更改提交到數據庫之前排序波蘇斯,當你使用「-1,-2,-3」,波蘇斯被保存翻轉,這在某些情況下(不按照什麼類型)可能並不理想。

public abstract class IdBase 
{ 
    public virtual int Id { get; set; } 
    protected IdBase() 
    { 
    Id = IdKeeper.Next(); 
    } 
} 

如果POCO從DB物化,他的身份證將與實際PK覆蓋,以及當你調用的SaveChanges爲()。而作爲獎金,每一個「尚未保存」 POCO ID將是唯一的(應該來方便休假一天;))

比較兩個POCO與IEquatable(why does dbset work so slow)是那麼容易:

public class Person 
    : IdBase, IEquatable<Person> 
{ 
    public virtual string FirstName { get; set; } 

    public bool Equals(Person other) 
    { 
    return Id == other.Id; 
    } 
} 
3

我遇到了你的問題,尋找同一個問題的解決方案。下面是我想出來,看它是否符合您的需求的解決方案:

首先,我所有的波蘇斯從這個抽象類派生:

public abstract class BasePOCO <T> : IEquatable<T> where T : class 
{ 
    private readonly Guid _guid = Guid.NewGuid(); 

    #region IEquatable<T> Members 

    public abstract bool Equals(T other); 

    #endregion 

    public override bool Equals(object obj) 
    { 
     if (ReferenceEquals(null, obj)) 
     { 
      return false; 
     } 
     if (ReferenceEquals(this, obj)) 
     { 
      return true; 
     } 
     if (obj.GetType() != typeof (T)) 
     { 
      return false; 
     } 
     return Equals((T)obj); 
    } 

    public override int GetHashCode() 
    { 
     return _guid.GetHashCode(); 
    } 
} 

我創造,我是一個只讀GUID字段在GetHashCode()覆蓋中使用。這將確保我將派生的POCO放入字典或其他使用散列的其他內容中,如果我在過渡期間調用了.SaveChanges(),並且ID字段已由基類更新,我不會孤兒它是我不確定的完全正確的一部分,還是比Base.GetHashCode()更好?。我抽象了Equals(T other)方法,以確保實現類必須以一種有意義的方式實現它,最有可能是使用ID字段。我把Equals(object obj)覆蓋到了這個基類中,因爲它對於所有的派生類也可能是相同的。

這將是抽象類的實現:

public class Species : BasePOCO<Species> 
{ 
    public int ID { get; set; } 
    public string LegacyCode { get; set; } 
    public string Name { get; set; } 

    public override bool Equals(Species other) 
    { 
     if (ReferenceEquals(null, other)) 
     { 
      return false; 
     } 
     if (ReferenceEquals(this, other)) 
     { 
      return true; 
     } 
     return ID != 0 && 
       ID == other.ID && 
       LegacyCode == other.LegacyCode && 
       Name == other.Name; 
    } 
} 

ID屬性設置爲數據庫中的主鍵和EF知道。在新創建的對象上ID爲0,然後在.SaveChanges()上設置爲唯一的正整數。所以在重寫的Equals(Species other)方法中,null對象顯然不相等,顯然是相同的引用,那麼我們只需要檢查ID == 0。如果是,我們會說兩個相同類型的對象兩個ID都是0不等於。否則,如果它們的屬性都相同,我們會說它們是平等的。

我認爲這涵蓋了所有相關的情況,但如果我不正確,請給我打電話。希望這可以幫助。

===編輯1

我在想我的GetHashCode()方法是不正確的,我看着這個https://stackoverflow.com/a/371348/213169回答有關問題。上面的實現會違反約束條件,即返回Equals()== true的對象必須具有相同的哈希碼。

這是我第二次刺吧:

public abstract class BasePOCO <T> : IEquatable<T> where T : class 
{ 
    #region IEquatable<T> Members 

    public abstract bool Equals(T other); 

    #endregion 

    public abstract override bool Equals(object obj); 
    public abstract override int GetHashCode(); 
} 

和實現:

public class Species : BasePOCO<Species> 
{ 
    public int ID { get; set; } 
    public string LegacyCode { get; set; } 
    public string Name { get; set; } 

    public override bool Equals(Species other) 
    { 
     if (ReferenceEquals(null, other)) 
     { 
      return false; 
     } 
     if (ReferenceEquals(this, other)) 
     { 
      return true; 
     } 
     return ID != 0 && 
     ID == other.ID && 
     LegacyCode == other.LegacyCode && 
     Name == other.Name; 
    } 

    public override bool Equals(object obj) 
    { 
     if (ReferenceEquals(null, obj)) 
     { 
      return false; 
     } 
     if (ReferenceEquals(this, obj)) 
     { 
      return true; 
     } 
     return Equals(obj as Species); 
    } 

    public override int GetHashCode() 
    { 
     unchecked 
     { 
      return ((LegacyCode != null ? LegacyCode.GetHashCode() : 0) * 397)^
        (Name != null ? Name.GetHashCode() : 0); 
     } 
    } 

    public static bool operator ==(Species left, Species right) 
    { 
     return Equals(left, right); 
    } 

    public static bool operator !=(Species left, Species right) 
    { 
     return !Equals(left, right); 
    } 
} 

所以我在基類中擺脫GUID的和移動的GetHashCode的實現。因爲ID可能會改變(不想孤兒),所以我使用Resharper的GetHashCode實現,除ID以外的所有屬性。這將滿足上面相關答案中對平等的限制。

+1

這是一個有趣的方法。我會盡可能地嘗試並分享我的反饋。如果您在解決此問題時有任何其他見解,我將不勝感激。 – 2012-11-19 18:12:41