2016-01-21 76 views
6

Includo la proprietà di navigazione nella mia query con Include, in modo che non venga caricata in seguito. Ma non funziona, quando creo un oggetto wrapper anonimo con la proiezione Select.EF: proprietà di navigazione "Includi" quando si crea un oggetto wrapper con proiezione "Seleziona"

Lasciami mostrare l'esempio semplificato. L'entità:

public class UserEntity { 
    public string Name {get;set;} 
    public virtual ICollection<UserEntity> Friends { get; set; } 
} 

La query:

var entry = _dbCtx 
    .Users 
    .Include(x => x.Friends) 
    // Select here is simplified, but it shows the wrapping 
    .Select(user => new { 
     User = user 
    }) 
    .First(); 

// Here we have additional lazy loaded DB call 
var friends = entry.User.Friends.Select(x => x.Name).ToList(); 

E vedo anche da SQL generato, che la proprietà di navigazione non è incluso:

SELECT 
    [Limit1].[Name] AS [Name], 
    FROM (SELECT TOP (1) 
     [Extent1].[Name] AS [Name] 
     FROM [dbo].[Users] AS [Extent1] 
    ) AS [Limit1] 

E 'possibile a Include la proprietà di navigazione Friends in questo caso, in modo che User otterrà i dati senza il caricamento lazy?

mi aspettavo anche questo lavoro:

var entry = _dbCtx 
    .Users 
    .Select(user => new { 
     User = user 
    }) 
    .Include(x => x.User.Friends) 
    .First(); 

Ma ottenere un'eccezione:

InvalidOperationException: Il tipo di risultato della query non è né un EntityType né una CollectionType con un tipo di elemento un'entità . Un percorso di inclusione può essere specificato solo per una query con uno di questi tipi di risultati.

Ci sono alcune soluzioni sono venuto a, ma sono in qualche modo ingannevole:

  1. Aggiungi proprietà aggiunta al nostro oggetto anonimo in Select:

    var entry = _dbCtx 
        .Users 
        .Select(user => new { 
         User = user, 
         UsersFriends = user.Friends 
        }) 
        .First(); 
    
    // manually copy the navigation property 
    entry.User.Friends = user.UsersFriends; 
    
    // Now we don't have any addition queries 
    var friends = entry.User.Friends.Select(x => x.Name).ToList(); 
    
  2. Map anche l'utente a un oggetto anonimo a livello di DB, quindi mappare le proprietà su UserEntity in C#.

    var entry = _dbCtx 
        .Users 
        .Select(user => new { 
         User = new { 
          Name = user.Name, 
          Friends = user.Friends 
         } 
        }) 
        .Take(1) 
        // Fetch the DB 
        .ToList() 
        .Select(x => new { 
         User = new UserEntity { 
          Name = x.Name, 
          Friends = x.Friends 
         } 
        }) 
        .First(); 
    
    // Now we don't have any addition queries 
    var friends = entry.User.Friends.Select(x => x.Name).ToList(); 
    

Così ora, c'è un LEFT OUTER JOIN per Friends, ma entrambe le soluzioni alternative non sono abbastanza buoni:

1) Ulteriori oggetti e una copia non è un modo pulito.

2) My UserEntity ha molte altre proprietà. Inoltre, ogni volta che aggiungiamo nuove proprietà, dovremmo modificare qui anche i selettori.

C'è un modo per ottenere la proprietà di navigazione incluso dal primo campione?

Grazie per la lettura e spero che qualcuno abbia un indizio per questo.

EDIT:

io dirigerò la l'entità e la query per mostrare un vero e proprio caso d'uso.

L'Entità

public class UserEntity { 
    public string Name {get;set;} 
    public int Score {get;set;} 
    public virtual ICollection<UserEntity> Friends { get; set; } 
} 

La query

var entry = _dbCtx 
    .Users 
    .Include(x => x.Friends) 
    .Select(user => new { 
     User = user, 
     Position = _dbCtx.Users.Count(y => y.Score > user.Score) 
    }) 
    .First(); 

risposta

0

Non è una risposta da _perché_ ma volevano una migliore formattazione del codice ...

Sono davvero sorpreso che funzioni in questo modo. Forse EF sta rilevando che non stai usando la proprietà Friends direttamente nella tua proiezione e quindi la stai ignorando. Che cosa succede se si incapsulato l'oggetto (s) di fuori della query EF:

var entry = _dbCtx 
    .Users 
    .Include(x => x.Friends) 
    .Take(1); // replicate "First" inside the EF query to reduce traffic 
    .AsEnumerable() // shift to linq-to-objects 
    // Select here is simplified, but it shows the wrapping 
    .Select(user => new { 
     User = user 
    }) 
    .First() 
+0

tuo campione funzionerà come si crea un oggetto wrapper sul lato 'C#' (dopo query), non con 'sql'. Ma ne avevo bisogno al livello 'db'. Motivo: eseguo subquery aggiuntive con 'Select'. Cosa interessante, vedi la mia prima soluzione, anche se usiamo 'user.Friends' nella query, ma dobbiamo copiare la proprietà su entità dopo la materializzazione, altrimenti avremmo ancora il caricamento lazy. – tenbits

+0

Perché è necessario proiettare su un tipo anonimo nella query db? Finisci con lo stesso risultato per quanto posso dire. O stai proiettando i metodi _additional_ ('Where',' OrderBy', ecc.) Che vuoi proiettare su SQL? –

+0

Vedere le mie modifiche post, eseguo una sottoquery per un utente e dovrebbe essere eseguita in DB, poiché scorre su ciascun utente. Quindi, a causa delle prestazioni, non posso farlo con 'C#'. – tenbits