Sto includendo 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
.
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 è inclusa:
SELECT
[Limit1].[Name] AS [Name],
FROM ( SELECT TOP (1)
[Extent1].[Name] AS [Name]
FROM [dbo].[Users] AS [Extent1]
) AS [Limit1]
È possibile Include
la proprietà di navigazione Friends
in questo caso, in modo che l' User
ottenga i dati senza caricare lentamente?
Mi aspettavo anche che questo funzionasse:
var entry = _dbCtx
.Users
.Select(user => new {
User = user
})
.Include(x => x.User.Friends)
.First();
Ma ottenendo un'eccezione:
InvalidOperationException: il tipo di risultato della query non è né un EntityType né un CollectionType con un tipo di elemento entità. Un percorso di inclusione può essere specificato solo per una query con uno di questi tipi di risultati.
Ci sono alcuni workaround a cui sono arrivato, ma sono in qualche modo ingannevoli:
Aggiungi proprietà di addizione 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();
Mappare anche Utente su 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();
Quindi, ora esiste un LEFT OUTER JOIN
per gli Friends
, ma entrambi i workaround non sono abbastanza buoni:
1) Proprietà aggiuntive 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.
MODIFICARE:
Estenderò l'entità e la query per mostrare un caso d'uso reale.
L'entità
public class UserEntity {
public string Name {get;set;}
public int Score {get;set;}
public virtual ICollection<UserEntity> Friends { get; set; }
}
La domanda
var entry = _dbCtx
.Users
.Include(x => x.Friends)
.Select(user => new {
User = user,
Position = _dbCtx.Users.Count(y => y.Score > user.Score)
})
.First();
Non è una risposta per _why_ ma voleva 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. Cosa succede se hai incapsulato l'oggetto (i) al 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()