EF: Incluir con la cláusula donde [duplicado]

Resuelto Beejee asked hace 11 años • 5 respuestas

Como sugiere el título, estoy buscando una manera de hacer una cláusula donde en combinación con una inclusión.

Aquí está mi situación: soy responsable del soporte de una aplicación grande llena de olores de código. Cambiar demasiado código provoca errores en todas partes, así que estoy buscando la solución más segura.

Digamos que tengo un objeto Bus y un objeto People (Bus tiene una colección de personas de accesorios de navegación). En mi Consulta necesito seleccionar todos los Autobuses con solo los Pasajeros que estén despiertos. Este es un ejemplo ficticio simplista.

En el código actual:

var busses = Context.Busses.Where(b=>b.IsDriving == true);
foreach(var bus in busses)
{
   var passengers = Context.People.Where(p=>p.BusId == bus.Id && p.Awake == true);
   foreach(var person in passengers)
   {
       bus.Passengers.Add(person);
   }
}

Después de este código, se elimina el contexto y, en el método de llamada, las entidades de bus resultantes se asignan a una clase DTO (copia 100% de la entidad).

Este código provoca múltiples llamadas a la base de datos, lo cual es No-Go, así que encontré esta solución en los blogs de MSDN.

Esto funcionó muy bien al depurar el resultado, pero cuando las entidades se asignan al DTO (usando AutoMapper), aparece una excepción que indica que el contexto/conexión se ha cerrado y que el objeto no se puede cargar. (El contexto siempre está cerrado, no se puede cambiar esto :()

Por lo tanto, necesito asegurarme de que los Pasajeros seleccionados ya estén cargados (IsLoaded en la propiedad de navegación también es Falso). Si inspecciono la colección de Pasajeros, The Count también arroja la excepción, pero también hay una colección en la Colección de Pasajeros llamada "entidades relacionadas envueltas" que contiene mis objetos filtrados.

¿Hay alguna manera de cargar estas entidades relacionadas envueltas en toda la colección? (No puedo cambiar la configuración de mapeo del automatapper porque se usa en toda la aplicación).

¿Existe otra forma de conseguir pasajeros activos?

Cualquier pista es bienvenida...

Editar

La respuesta de Gert Arnold no funciona porque los datos no se cargan con entusiasmo. Pero cuando lo simplifico y elimino el lugar donde está cargado. Esto es realmente extraño ya que ejecutar sql devuelve todos los pasajeros en ambos casos. Entonces debe haber un problema al devolver los resultados a la entidad.

Context.Configuration.LazyLoadingEnabled = false;
var buses = Context.Busses.Where(b => b.IsDriving)
        .Select(b => new 
                     { 
                         b,
                         Passengers = b.Passengers
                     })
        .ToList()
        .Select(x => x.b)
        .ToList();

Editar2

¡Después de mucha lucha, la respuesta de Gert Arnold funcionó! Como sugirió Gert Arnold, es necesario desactivar la carga diferida y mantenerla desactivada. Esto solicitará algunos cambios adicionales en la aplicación, ya que al desarrollador anterior le encantó la carga diferida -_-

Beejee avatar May 29 '13 01:05 Beejee
Aceptado

Esta característica ahora se ha agregado al núcleo 5 de Entity Framework . Para versiones anteriores necesita una solución alternativa (tenga en cuenta que EF6 es una versión anterior).

Solución alternativa de Entity Framework 6

En EF6 , una solución alternativa es consultar primero los objetos requeridos en una proyección ( new) y dejar que la reparación de relaciones haga su trabajo.

Puede consultar los objetos requeridos por

Context.Configuration.LazyLoadingEnabled = false;
// Or: Context.Configuration.ProxyCreationEnabled = false;
var buses = Context.Busses.Where(b => b.IsDriving)
            .Select(b => new 
                         { 
                             b,
                             Passengers = b.Passengers
                                           .Where(p => p.Awake)
                         })
            .AsEnumerable()
            .Select(x => x.b)
            .ToList();

Lo que sucede aquí es que primero recupera los autobuses que conducen y despierta a los pasajeros de la base de datos. Luego, AsEnumerable()cambia de LINQ a Entidades a LINQ a objetos, lo que significa que los autobuses y pasajeros se materializarán y luego se procesarán en la memoria. Esto es importante porque sin él EF sólo materializará la proyección final, Select(x => x.b)no los pasajeros.

Ahora EF tiene esta característica de corrección de relaciones que se encarga de establecer todas las asociaciones entre objetos que se materializan en el contexto. Esto significa que Busahora solo se cargan en cada uno de ellos sus pasajeros despiertos.

Cuando obtienes la colección de autobuses, ToListtienes los autobuses con los pasajeros que deseas y puedes mapearlos con AutoMapper.

Esto sólo funciona cuando la carga diferida está desactivada. De lo contrario, EF cargará de forma diferida a todos los pasajeros de cada autobús cuando se acceda a los pasajeros durante la conversión a DTO.

Hay dos formas de desactivar la carga diferida. La desactivación LazyLoadingEnabledreactivará la carga diferida cuando se habilite nuevamente. La desactivación ProxyCreationEnabledcreará entidades que no son capaces de realizar una carga diferida por sí mismas , por lo que no comenzarán la carga diferida después de ProxyCreationEnabledhabilitarla nuevamente. Esta puede ser la mejor opción cuando el contexto dura más que esta única consulta.

Pero... muchos a muchos

Como se dijo, esta solución alternativa se basa en arreglar la relación. Sin embargo, como explica Slauma aquí , la reparación de relaciones no funciona con asociaciones de muchos a muchos. Si - es de muchos a muchos, lo único que puedes hacer es arreglarlo tú mismo:BusPassenger

Context.Configuration.LazyLoadingEnabled = false;
// Or: Context.Configuration.ProxyCreationEnabled = false;
var bTemp = Context.Busses.Where(b => b.IsDriving)
            .Select(b => new 
                         { 
                             b,
                             Passengers = b.Passengers
                                           .Where(p => p.Awake)
                         })
            .ToList();
foreach(x in bTemp)
{
    x.b.Pasengers = x.Passengers;
}
var busses = bTemp.Select(x => x.b).ToList();

...y todo se vuelve aún menos atractivo.

Herramientas de terceros

Hay una biblioteca, EntityFramework.DynamicFilters que hace esto mucho más fácil. Le permite definir filtros globales para entidades, que posteriormente se aplicarán cada vez que se consulte la entidad. En su caso esto podría verse así:

modelBuilder.Filter("Awake", (Person p) => p.Awake, true);

Ahora si lo haces...

Context.Busses.Where(b => b.IsDriving)
       .Include(b => b.People)

...verás que el filtro se aplica a la colección incluida.

También puede habilitar/deshabilitar filtros, para tener control sobre cuándo se aplican. Creo que esta es una biblioteca muy ordenada.

Existe una biblioteca similar del fabricante de AutoMapper: EntityFramework.Filters

Solución alternativa principal de Entity Framework

Desde la versión 2.0.0, EF-core tiene filtros de consulta globales . Estos se pueden utilizar para establecer filtros predefinidos en las entidades que se incluirán. Por supuesto, eso no ofrece la misma flexibilidad que filtrar Includesobre la marcha. Aunque los filtros de consulta globales son una gran característica, hasta ahora la limitación es que un filtro no puede contener referencias a propiedades de navegación, solo a la entidad raíz de una consulta. Con suerte, en versiones posteriores estos filtros lograrán un uso más amplio.

Gert Arnold avatar May 28 '2013 20:05 Gert Arnold

Ahora el método Filter Include de EF Core 5.0 ahora admite el filtrado de las entidades incluidas

var busses = _Context.Busses
                .Include(b => b.Passengers
                                       .Where(p => p.Awake))
            .Where(b => b.IsDriving);
Satpal avatar Dec 21 '2020 05:12 Satpal

Descargo de responsabilidad : soy el propietario del proyecto Entity Framework Plus

La función EF+ Query IncludeFilter permite filtrar entidades relacionadas.

var buses = Context.Busses
                   .Where(b => b.IsDriving)
                   .IncludeFilter(x => x.Passengers.Where(p => p.Awake))
                   .ToList();

Wiki: Consulta EF+ Incluir filtro

Jonathan Magnan avatar Mar 14 '2017 12:03 Jonathan Magnan