2012-03-20 18 views
6

Ho notato che DbSet.Add() di EF è piuttosto lento. Un po 'googling alzato una risposta così che promette fino a 180x guadagni di prestazioni:Implement IEquatable for POCO

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

Tuttavia, non capisco esattamente come implementare IEquatable<T> come suggerito nella risposta.

According to MSDN, se implemento IEquatable<T>, dovrei anche ignorare Equals() e GetHashCode().

Come per molti POCO, i miei oggetti sono mutabili. Prima di essere impegnati nel database (SaveChanges()), i nuovi oggetti hanno un ID di 0. Dopo gli oggetti sono stati salvati, l'ID funge da base ideale per l'implementazione di IEquatable, Equals() e GetHashCode().

Non è saggio includere qualsiasi struttura mutevole in un codice hash, e poiché according to MSDN

Se due oggetti risultano uguali, il metodo GetHashCode per ogni oggetto deve restituire lo stesso valore

Devo implementare IEquatable<T> come confronto proprietà per proprietà (ad es. this.FirstName == other.FirstName) e non eseguire l'override di Equals() e GetHashCode()?

Dato che i miei POCO sono utilizzati in un contesto EntityFramework, è necessario prestare particolare attenzione al campo Id?

+0

Perché i tuoi oggetti hanno un ID 0? Perché non come JomTois mostra nel suo esempio di codice direttamente assegnare un Guid a un campo ID/Proprietà. Questa è una base ideale per la tua IEquatable che menzioni tu stesso. Anche TomTom indica i metodi di assegnazione di un intervallo di identificazione per ogni cliente o utilizzando -1, -2 e -3 come ID temporanei, queste "soluzioni" sembrano complemento delle cose. Al momento della costruzione si genera una nuova guida e sono in attività. O mi manca qualcosa? –

+0

@YoupTube: i numeri interi non possono essere senza valore. Inoltre, gli interi sono a 32 bit mentre i GUID sono a 128 bit. Ciò significa che SQL Server può contenere fino a 4 ID quanti in memoria utilizzando un numero intero (chiave per le prestazioni quando si eseguono i join). –

+0

Ah, capisco. Ma hai un sacco di dati, traffico, join e hai bisogno di prestazioni altissime? È sempre un compromesso ... lo so. Ma usare guid non dovrebbe essere un grosso problema. E poi puoi dimenticare tutti i problemi complessi che hai a causa dell'uso di interi. –

risposta

1

Come per molti POCO di, i miei oggetti sono mutabili

Ma mattonelle maiolicate non deve essere mutabile sui campi che sono la chiave primaria. Per definizione, o sei in un mondo di database del dolore saggio comunque più tardi.

Generare HashCode SOLO sui campi del tasto del primay.

Equals() deve restituire true IFF gli oggetti partecipanti hanno lo stesso codice hash

Bzzz - Errore.

I codici hash sono doppi. È possibile che 2 oggetti abbiano valori diversi e l'hashcode di smae. Un hsahsode è un int (32 bit). Una stringa può essere lunga 2 GB. Non è possibile mappare tutte le stringhe su un hashcode separato.

Se due oggetti hanno lo stesso codice hash, possono essere diversi. Se due oggetti sono uguali, NON possono avere hash diversi.

Da dove viene l'idea che Equals deve restituire true per oggetti con lo stesso hashcode?

Inoltre, PCO o no, un oggetto mappato su un database e utilizzato in una relazione DEVE avere una chiave primaria stabile (che può essere utilizzata per eseguire il calcolo dell'hashcode). Un oggetto che non ha questo STIL dovrebbe avere la chiave primaria (per i requisiti di SQL Server), usando una sequenza/chiave primaria artificiale funziona qui.Ancora una volta, usalo per eseguire il calcolo HashCode.

+0

Hai ragione, la relazione tra Equals e GetHashCode è il contrario "Se due oggetti vengono confrontati come uguali, il metodo GetHashCode per ogni oggetto deve restituire lo stesso valore" http://msdn.microsoft.com/en-us /library/system.object.gethashcode.aspx. Per quanto riguarda la chiave: una volta inserito un oggetto nel DB, identità e uguaglianza sono semplici. Nel mio caso, sto creando un'istanza di nuovi oggetti e non ho ancora chiamato 'SaveChanges()', quindi tutti gli ID sono 0. Modificherò la mia domanda in base ai tuoi commenti, ma non vedo ancora una soluzione che supporti nuovi oggetti senza PK assegnato. –

+1

utilizza le chiavi primarie generate dal cliente. GUID # s, una sequenza generata sul lato client. Altrimenti i soliti meccanismi di riferimento sono dolorosi. O - vai con i propri ID. Il mio ORM qualche anno fa usava numeri negativi (-1, -2, -3) come chiavi di tempoarry, che si sostituivano nell'inserto. GThe gli hashcode non sono comunque legalmente riutilizzabili dopo il commit (gli oggetti devono essere aggiornati);) Problema risolto. – TomTom

+0

+1 per aver menzionato che hashcode uguali non significano automaticamente che i tuoi oggetti sono gli stessi. – VVS

0

Per prima cosa: Scusate il mio inglese zoppicante :)

Come dire TomTom, che non dovrebbe essere mutabile solo perché non hanno ancora ricevuto PK/Id ...

Nel nostro EF: sistema di CF , utilizziamo l'ID negativo generato (assegnato nel class class ctor o, se si utilizza ProxyTracking, nell'evento ObjectMaterialized) per ogni nuovo POCO. La sua abbastanza semplice idea:

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

MinValue e incremen dovrebbe essere importante, perché EF ordinerà pocos dal loro PK prima di commettere modifiche al db e quando usare "-1, -2, -3", pocos vengono salvati capovolti, che in alcuni casi (non a seconda del tipo) potrebbero non essere ideali.

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

Se POCO è materializzato da DB, la sua identificazione sarà precedenza con l'attuale PK così come quando si chiama SaveChanges(). E come bonus, ogni singolo "non ancora salvato" POCO id sarà unico (che dovrebbe tornare utile un giorno,))

confronto tra due POCO con IEquatable (why does dbset work so slow) è quindi facile:

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

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

I ha trovato la tua domanda in cerca di una soluzione alla stessa domanda. Ecco una soluzione che sto provando, vedere se soddisfa le vostre esigenze:

In primo luogo, tutti i miei pocos derivano da questa classe astratta:

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

ho creato un campo di sola lettura Guid che io sono utilizzando l'override GetHashCode(). Ciò assicurerà che se dovessi inserire il POCO derivato in un dizionario o qualcos'altro che utilizza l'hash, non lo farei orfano se avessi chiamato un .SaveChanges() nel frattempo e il campo ID fosse aggiornato dalla classe base è l'unica parte che non sono sicuro è completamente corretta, o se è meglio di Base.GetHashCode()?. Ho astratto il metodo Equals (T other) per garantire che le classi di implementazione dovessero implementarlo in modo significativo, molto probabilmente con il campo ID. Ho inserito l'override di Equals (object obj) in questa classe base perché probabilmente sarebbe lo stesso per tutte le classi derivate.

questo sarebbe un implementazione della classe astratta:

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

La proprietà ID è impostato come chiave primaria nel database e EF sa che. L'ID è 0 su un oggetto appena creato, quindi viene impostato su un intero positivo univoco su .SaveChanges(). Quindi nel metodo Equals override (Species other), gli oggetti nulli non sono ovviamente uguali, ovviamente lo stesso riferimento è, quindi dobbiamo solo verificare se l'ID == 0. Se lo è, diremo che due oggetti dello stesso tipo che entrambi hanno ID 0 non sono uguali. Altrimenti, diremo che sono uguali se le loro proprietà sono tutte uguali.

Penso che questo copra tutte le situazioni rilevanti, ma per favore inseriscimi se non sono corretto. Spero che questo ti aiuti.

=== Modifica 1

Stavo pensando il mio GetHashCode() non era giusto, e ho guardato questo https://stackoverflow.com/a/371348/213169 risposta per quanto riguarda il soggetto.L'implementazione sopra violerebbe il vincolo che gli oggetti che restituiscono Equals() == true devono avere lo stesso hashcode.

Qui è la mia seconda pugnalata a lui:

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

e l'implementazione:

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

Così mi sono liberato del GUID nella classe base e si è trasferito GetHashCode alla realizzazione. Ho usato l'implementazione di GetHashCode di Resharper con tutte le proprietà tranne l'ID, dato che l'ID potrebbe cambiare (non voglio orfani). Questo soddisferà il vincolo sull'eguaglianza nella risposta collegata sopra.

+1

Questo è un approccio interessante. Farò un tentativo quando posso e condividere il mio feedback. Gradirei qualsiasi ulteriore intuizione che hai mentre lavori su questo problema. –