EF: Incluir com cláusula where

Como o título sugere, estou procurando uma maneira de fazer uma cláusula where em combinação com uma inclusão.

Aqui estão as minhas situações: Eu sou responsável pelo suporte de um grande aplicativo cheio de cheiros de código. Mudar muito código causa bugs em todos os lugares, então estou procurando a solução mais segura.

Digamos que eu tenha um object Bus e um object People (o Bus possui uma coleção de People). Na minha consulta eu preciso selecionar todos os ônibus com apenas os passageiros que estão acordados. Este é um exemplo fictício simplista

No código atual:

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); } } 

Após esse código, o Contexto é descartado e, no método de chamada, as entidades de Barramento resultantes são Mapeadas para uma class DTO (cópia de 100% da Entidade).

Este código faz com que várias chamadas para o database que é um No-Go, então eu encontrei esta solução em blogs do MSDN

Isso funcionou muito bem ao depurar o resultado, mas quando as entidades são mapeadas para o DTO (usando o AutoMapper), recebo uma exceção que o contexto / conexão foi fechado e que o object não pode ser carregado. (Contexto está sempre fechado não pode mudar isso :()

Então eu preciso ter certeza de que os Passengers Selecionados já estão carregados (IsLoaded na propriedade de navegação também é False). Se eu inspecionar a coleta de Passageiros, o Count também lança a Exceção, mas há também uma coleção na Coleção de Passageiros, chamada “entidades relacionadas agrupadas”, que contém meus objects filtrados.

Existe uma maneira de carregar essas entidades relacionadas empacotadas em toda a coleção? (Não posso alterar a configuração do mapeamento do automapper porque isso é usado em todo o aplicativo).

Existe outra maneira de obter os passageiros ativos?

Qualquer dica é bem vinda …

Editar

Resposta de Gert Arnold não funciona porque os dados não são carregados ansiosamente. Mas quando eu simplifico e excluo o local onde ele é carregado. Isso é muito estranho, já que o sql execute retorna todos os passageiros em ambos os casos. Portanto, deve haver um problema ao colocar os resultados de volta na entidade.

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

Edit2

Depois de muita luta, a resposta de Gert Arnold funciona! Como Gert Arnold sugeriu que você precisa desativar o Lazy Loading e mantê-lo desligado. Isso pedirá algumas mudanças extras no appliaction, já que o desenvolvedor anterior adorava o Lazy Loading -_-

Você pode consultar os objects necessários

 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 => xb) .ToList(); 

O que acontece aqui é que você primeiro pega os ônibus e acorda os passageiros do database. Em seguida, AsEnumerable() alterna de LINQ para Entities para LINQ para objects, o que significa que os ônibus e passageiros serão materializados e, em seguida, processados ​​na memory. Isso é importante porque sem ele a EF só materializará a projeção final, Select(x => xb) , não os passageiros.

Agora, o EF tem esse recurso de relacionamento de resources que cuida de definir todas as associações entre os objects que são materializados no contexto. Isso significa que para cada Bus agora apenas os passageiros acordados são carregados.

Quando você pegar a coleção de ônibus por ToList você tem os ônibus com os passageiros que você quer e você pode mapeá-los com AutoMapper.

Isso só funciona quando o carregamento lento está desativado. Caso contrário, a EF preguiçosa carregará todos os passageiros para cada ônibus quando os passageiros forem acessados ​​durante a conversão para DTOs.

Existem duas maneiras de desativar o carregamento lento. Desativar o LazyLoadingEnabled reativará o carregamento lento quando for ativado novamente. Desativar ProxyCreationEnabled criará entidades que não são capazes de carregar com preguiça, portanto, elas não iniciarão o carregamento lento depois que ProxyCreationEnabled for ativado novamente. Essa pode ser a melhor escolha quando o contexto viver mais do que apenas essa consulta única.

Mas … muitos-para-muitos

Como dito, essa solução depende da correção de relacionamento. No entanto, conforme explicado aqui por Slauma , o ajuste de relacionamento não funciona com associações de muitos para muitos. Se o BusPassenger for muitos-para-muitos, a única coisa que você pode fazer é corrigi-lo:

 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) { xbPasengers = x.Passengers; } var busses = bTemp.Select(x => xb).ToList(); 

… e a coisa toda se torna ainda menos atraente.

Ferramentas de terceiros

Existe uma biblioteca, EntityFramework.DynamicFilters , que facilita muito isso. Ele permite que você defina filtros globais para entidades, que serão subsequentemente aplicadas sempre que a entidade for consultada. No seu caso, isso poderia parecer:

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

Agora se você fizer …

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

… você verá que o filtro é aplicado à coleção incluída.

Você também pode ativar / desativar filtros, para ter controle sobre quando eles são aplicados. Eu acho que esta é uma biblioteca muito legal.

Há uma biblioteca semelhante do criador do AutoMapper: EntityFramework.Filters

Núcleo do Entity Framework

Desde a versão 2.0.0, o EF-core possui filtros de consulta em nível de modelo . Embora este seja um ótimo complemento para seus resources, até agora a limitação é que ele não pode ser aplicado às propriedades de navegação, apenas para a entidade raiz de uma consulta. Espero que na versão posterior esses filtros alcancem um uso mais amplo.

Isenção de responsabilidade : Sou o proprietário do projeto Entity Framework Plus

O recurso 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: EF + Query IncludeFilter