c# - multiple - EF: propiedad de navegación "Include", al crear un objeto contenedor con proyección "Select"
linq to entities (1)
No una respuesta sobre _por qué_ pero quería un mejor formato de código ...
De hecho, me sorprende que funcione de esa manera. Quizás EF está detectando que no está utilizando la propiedad Friends
directamente en su proyección y, por lo tanto, la está ignorando. ¿Qué sucede si encapsulas el (los) objeto (s) fuera de la consulta 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()
Incluyo la propiedad de navegación en mi consulta con Include
, para que no se cargue de forma diferida más tarde. Pero no funciona, cuando creo un wrapper-objeto anónimo con Select
projection.
Déjame mostrar el ejemplo simplificado. La entidad :
public class UserEntity {
public string Name {get;set;}
public virtual ICollection<UserEntity> Friends { get; set; }
}
La consulta :
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();
Y también veo desde SQL generado, que la propiedad de navegación no está incluida:
SELECT
[Limit1].[Name] AS [Name],
FROM ( SELECT TOP (1)
[Extent1].[Name] AS [Name]
FROM [dbo].[Users] AS [Extent1]
) AS [Limit1]
¿Es posible Include
la propiedad de navegación Friends
en este caso, para que el User
obtenga los datos sin carga diferida?
También esperaba que esto funcione:
var entry = _dbCtx
.Users
.Select(user => new {
User = user
})
.Include(x => x.User.Friends)
.First();
Pero obteniendo una excepción:
InvalidOperationException: el tipo de resultado de la consulta no es un EntityType ni un CollectionType con un tipo de elemento de entidad. Una ruta de inclusión solo se puede especificar para una consulta con uno de estos tipos de resultados.
Hay algunas soluciones provisionales a las que llegué, pero son de alguna manera complicadas:
Añada propiedad adicional a nuestro objeto anónimo en
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();
Asigne también el usuario a un objeto anónimo en el nivel de base de datos y luego
UserEntity
propiedades aUserEntity
en 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();
Así que ahora, hay una LEFT OUTER JOIN
para Friends
, pero ambas soluciones no son muy buenas:
1) Propiedades adicionales y una copia no es una manera limpia.
2) Mi UserEntity tiene muchas más otras propiedades. Además, cada vez que agreguemos nuevas propiedades, deberíamos modificar también los selectores aquí.
¿Hay alguna forma de lograr la propiedad de navegación, incluida la primera muestra?
Gracias por leer y espero que alguien tenga una pista para esto.
EDITAR:
Extenderé la entidad y la consulta para mostrar un caso de uso real.
La entidad
public class UserEntity {
public string Name {get;set;}
public int Score {get;set;}
public virtual ICollection<UserEntity> Friends { get; set; }
}
La consulta
var entry = _dbCtx
.Users
.Include(x => x.Friends)
.Select(user => new {
User = user,
Position = _dbCtx.Users.Count(y => y.Score > user.Score)
})
.First();