entity-framework linq-to-entities entity-framework-4.1

entity framework - Marco de la entidad: consultar entidades infantiles



entity-framework linq-to-entities (3)

¡Estoy aprendiendo sobre Entity Framework en el momento, y estoy teniendo problemas!

¿Alguien puede aclarar si estoy en lo cierto al pensar que no puedo obtener un padre y un subconjunto de sus hijos del DB?

Por ejemplo...

db.Parents .Include(p => p.Children) .Where(p => p.Children.Any(c => c.Age >= 5))

Esto devolverá a todos los padres que tengan un hijo de 5 años o más, pero si repito en la colección Parents.Children, todos los niños estarán presentes (no solo los mayores de 5 años).

Ahora la consulta tiene sentido para mí (¡he pedido incluir hijos y los tengo!), Pero puedo imaginar que me gustaría tener la cláusula where aplicada a la colección secundaria en algunos escenarios.

Preguntas:

  1. Es lo que dije correcto?
  2. ¿Es posible obtener los padres y solo un subconjunto de la base de datos sin realizar muchas llamadas a la base de datos?
  3. Estoy fuera de la marca? (No sería la primera vez) !!!!

He encontrado algunos blogs y publicaciones de SO que tratan sobre el tema, pero nada que lo explique lo suficientemente bien para mi pequeño cerebro.

EDITAR

Después de leer este blog (gracias a Daz Lewis) ... ¡todavía no lo entiendo!

En el ejemplo dado en el blog, puedo ver cómo puedo lograrlo en una sola instancia de Parent, pero estoy luchando para encontrar la forma de hacerlo con una colección.

¿Cómo podría obtener un IEnumerable, en el que cada uno de los padres tiene una colección filtrada de niños (edad> = 5)?

Más aclaraciones:

En respuesta al comentario de DonAndre, estoy buscando a) Una lista de padres que tienen un hijo mayor de 5 (e incluyen solo a esos niños).

Cualquier ayuda apreciada,

Gracias.


Creo que los padres y el niño no son muy adecuados como entidades separadas. Un niño siempre puede ser un padre y generalmente un niño tiene dos padres (un padre y una madre), por lo que no es el contexto más simple. Pero supongo que tienes una simple relación 1: n como en el siguiente modelo maestro-esclavo que utilicé.

Lo que tienes que hacer es hacer una combinación externa izquierda (esa respuesta me ha llevado por el camino correcto). Tal unión es un poco difícil de hacer, pero aquí está el código

var query = from m in ctx.Masters join s in ctx.Slaves on m.MasterId equals s.MasterId into masterSlaves from ms in masterSlaves.Where(x => x.Age > 5).DefaultIfEmpty() select new { Master = m, Slave = ms }; foreach (var item in query) { if (item.Slave == null) Console.WriteLine("{0} owns nobody.", item.Master.Name); else Console.WriteLine("{0} owns {1} at age {2}.", item.Master.Name, item.Slave.Name, item.Slave.Age); }

Esto se traducirá a la siguiente instrucción SQL con EF 4.1

SELECT [Extent1].[MasterId] AS [MasterId], [Extent1].[Name] AS [Name], [Extent2].[SlaveId] AS [SlaveId], [Extent2].[MasterId] AS [MasterId1], [Extent2].[Name] AS [Name1], [Extent2].[Age] AS [Age] FROM [dbo].[Master] AS [Extent1] LEFT OUTER JOIN [dbo].[Slave] AS [Extent2] ON ([Extent1].[MasterId] = [Extent2].[MasterId]) AND ([Extent2].[Age] > 5)

Tenga en cuenta que es importante realizar la cláusula Where adicional en la edad de la colección unida y no entre la de y la de selección.

EDITAR:

SI desea un resultado jerárquico, puede convertir la lista plana realizando una agrupación:

var hierarchical = from line in query group line by line.Master into grouped select new { Master = grouped.Key, Slaves = grouped.Select(x => x.Slave).Where(x => x != null) }; foreach (var elem in hierarchical) { Master master = elem.Master; Console.WriteLine("{0}:", master.Name); foreach (var s in elem.Slaves) // note that it says elem.Slaves not master.Slaves here! Console.WriteLine("{0} at {1}", s.Name, s.Age); }

Tenga en cuenta que utilicé un tipo anónimo para almacenar el resultado jerárquico. Por supuesto, también puede crear un tipo específico como este

class FilteredResult { public Master Master { get; set; } public IEnumerable<Slave> Slaves { get; set; } }

y luego proyecte el grupo en instancias de esta clase. Eso hace que sea más fácil si necesita pasar estos resultados a otros métodos.


La única forma de obtener una colección de padres con una colección de niños filtrada en una única base de datos es usando una proyección. No es posible utilizar la carga ansiosa ( Include ) porque no admite el filtrado. Include siempre carga toda la colección. La forma de carga explícita mostrada por @Daz requiere una ida y vuelta por entidad principal.

Ejemplo:

var result = db.Parents .Select(p => new { Parent = p, Children = p.Children.Where(c => c.Age >= 5) }) .ToList();

Puede trabajar directamente con esta colección de objetos de tipo anónimos. (También puede proyectar en su propio tipo con nombre en lugar de una proyección anónima (pero no en una entidad como Parent )).

El contexto de EF también completará automáticamente la colección de Children del Parent si no deshabilita el seguimiento de cambios (utilizando AsNoTracking() por ejemplo). En este caso, puede proyectar el elemento primario del tipo de resultado anónimo (sucede en la memoria, no consulta DB):

var parents = result.Select(a => a.Parent).ToList();

parents[i].Children niños contendrán sus hijos filtrados para cada Parent .

Edita hasta tu última edición en la pregunta:

Estoy buscando a) Una lista de padres que tienen un hijo mayor de 5 (e incluyen solo a esos niños).

El código anterior devolvería a todos los padres e incluiría solo a los niños con Age > = 5, por lo que posiblemente también los padres con una colección de niños vacíos si solo hay niños con Age <5. Puede filtrarlos usando una cláusula Where adicional para los padres para obtener solo los padres que tienen al menos un ( Any ) hijo con Age > = 5:

var result = db.Parents .Where(p => p.Children.Any(c => c.Age >= 5)) .Select(p => new { Parent = p, Children = p.Children.Where(c => c.Age >= 5) }) .ToList();


Tomando su ejemplo, lo siguiente debe hacer lo que necesita. Mire blog para más información.

db.Entry(Parents) .Collection("Children") .Query().Cast<Child>() .Where(c => c.Age >= 5)) .Load();