2014-08-27 15 views
6

Ieri stavo lavorando a un refactor del codice e ho trovato un'eccezione su cui non sono riuscito a trovare molte informazioni. Ecco la situazioneEccezione nella query Inner LINQ quando si chiama ToList()

Abbiamo una coppia di entità EF che hanno una relazione molte a molte attraverso una tabella delle relazioni. Gli oggetti in questione assomigliano a questo, tralasciando i bit non necessari.

public partial class MasterCode 
{ 
    public int MasterCodeId { get; set; } 
    ... 

    public virtual ICollection<MasterCodeToSubCode> MasterCodeToSubCodes { get; set; } 
} 

public partial class MasterCodeToSubCodes 
{ 
    public int MasterCodeToSubCodeId { get; set; } 
    public int MasterCodeId { get; set; } 
    public int SubCodeId { get; set; } 
    ... 
} 

Ora, ho tentato di eseguire una query LINQ contro queste entità. Utilizziamo molte proiezioni LINQ in DTO. Seguono il DTO e la query. masterCodeId è un parametro passato in.

public class MasterCodeDto 
{ 
    public int MasterCodeId { get; set; } 
    ... 

    public ICollection<int> SubCodeIds { get; set; } 
} 

(from m in MasterCodes 
where m.MasterCodeId == masterCodeId 
select new MasterCodeDto 
{ 
    ... 
    SubCodeIds = (from s in m.MasterCodeToSubCodes 
        select s.SubCodeId).ToList(), 
    ... 
}).SingleOrDefaultAsync(); 

La query interna getta la seguente eccezione

Expression of type 'System.Data.Entity.Infrastructure.ObjectReferenceEqualityComparer' cannot be used for constructor parameter of type 'System.Collections.Generic.IEqualityComparer`1[System.Int32]' 

Abbiamo fatto domande interne di simile prima in altri luoghi nel nostro codice e non ha avuto problemi. La differenza in questo è che non stiamo rinnovando un oggetto e proiettandolo ma piuttosto restituendo un gruppo di ints che vogliamo inserire in un elenco.

Ho trovato una soluzione alternativa modificando ICollection su MasterCodeDto in IEnumerable e rilasciando ToList() ma non sono mai riuscito a scoprire perché non potevo semplicemente selezionare gli ID e restituirli come elenco.

Qualcuno ha qualche idea in merito a questo problema? Normalmente restituendo solo un campo id e chiamando ToList() funziona bene quando non fa parte di una query interna. Mi manca una restrizione sulle query interne che impedisce che un'operazione come questa accada?

Grazie.

Modifica: per dare un esempio di dove questo schema funziona, ti mostrerò un esempio di una query che funziona.

(from p in Persons 
where p.PersonId == personId 
select new PersonDto 
{ 
    ... 
    ContactInformation = (from pc in p.PersonContacts 
          select new ContactInformationDto 
          { 
           ContactInformationId = pc.PatientContactId, 
           ... 
          }).ToList(), 
    ... 
    }).SingleOrDefaultAsync(); 

In questo esempio, stiamo selezionando un nuovo Dto anziché selezionare un singolo valore. Funziona bene. I problemi sembrano derivare dalla semplice selezione di un singolo valore.

Modifica 2: in un altro aspetto divertente, se invece di selezionare in un MasterCodeDto seleziono in un tipo anonimo, l'eccezione non viene generata con ToList() sul posto.

+1

Sei davvero sicuro che sia l''elenco degli utenti() 'a causare l'errore? – flindeberg

+0

Se rimuovo il ToList() l'eccezione scompare e ho appena ricevuto errori di cast tra IEnumerable e ICollection . Se cambio ICollection in IEnumerable elimina questo errore. –

+0

Anche quando lo enumerate in seguito? – flindeberg

risposta

7

Penso che ti sia imbattuto in un bug in Entity Framework. EF ha una certa logica per scegliere un tipo concreto appropriato per materializzare le collezioni. è uno dei suoi preferiti. Apparentemente (non posso seguire completamente il codice sorgente di EF qui) seleziona HashSet per ICollections e List per IEnumerable.

Sembra che EF cerchi di creare un HashSet utilizzando lo constructor che accetta uno IEqualityComparer<T>. (Questo accade nella classe DelegateFactory di EF, metodo GetNewExpressionForCollectionType.) L'errore è che utilizza il proprio ObjectReferenceEqualityComparer per questo. Ma questo è un IEqualityComparer<object>, che non può essere convertito in un IEqualityComparer<int>.

In generale, ritengo sia preferibile non utilizzare ToList nelle query LINQ e utilizzare IEnumerable nelle raccolte in tipi DTO. Pertanto, EF avrà la totale libertà di scegliere un tipo concreto appropriato.

+0

Questo è stato simile alla conclusione alla quale sono arrivato mentre scavo all'interno del codice EF. Penso che passare a IEnumerable sia probabilmente l'idea migliore a questo punto per evitare questo problema in futuro. Grazie per aver confermato ciò che sentivo era il problema. –

+0

Grazie. Ma ho bisogno di ICollection perché voglio usare i suoi metodi come Aggiungi, Rimuovi e così via. Quindi qualche altra speranza ?? –

Problemi correlati