C# EF:包含where子句

C# EF:包含where子句,c#,entity-framework,include,where-clause,C#,Entity Framework,Include,Where Clause,正如标题所示,我正在寻找一种结合include的where子句的方法 以下是我的情况: 我负责支持一个充满代码气味的大型应用程序。 更改太多代码会导致到处都有bug,所以我正在寻找最安全的解决方案 假设我有一个对象总线和一个对象人物(总线有一个导航道具人物集合)。 在我的查询中,我需要选择所有公交车,其中只有醒着的乘客。这是一个过于简单的虚拟示例 在当前代码中: var busses = Context.Busses.Where(b=>b.IsDriving == true); forea

正如标题所示,我正在寻找一种结合include的where子句的方法

以下是我的情况: 我负责支持一个充满代码气味的大型应用程序。 更改太多代码会导致到处都有bug,所以我正在寻找最安全的解决方案

假设我有一个对象总线和一个对象人物(总线有一个导航道具人物集合)。 在我的查询中,我需要选择所有公交车,其中只有醒着的乘客。这是一个过于简单的虚拟示例

在当前代码中:

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);
   }
}
在此代码之后,上下文被释放,在调用方法中,生成的总线实体被映射到DTO类(实体的100%副本)

这段代码导致对DB的多次调用,这是不可能的,所以我找到了这个解决方案

这在调试结果时非常有效,但是当实体映射到DTO(使用AutoMapper)时,我得到一个异常,即上下文/连接已关闭,无法加载对象。(上下文始终关闭无法更改此:()

因此,我需要确保已加载所选乘客(IsLoaded on navigation属性也为False)。如果我检查乘客集合,计数也会引发异常,但在名为“包装相关实体”(wrapped related entities)的乘客集合中也有一个集合,其中包含我的筛选对象

有没有办法将这些包装好的相关实体加载到整个集合中? (我无法更改automapper映射配置,因为它在整个应用程序中使用)

有没有其他办法让活跃的乘客上车

欢迎任何提示

编辑 格特·阿诺德的答案不起作用,因为数据没有被急切地加载。 但是当我简化它并删除加载它的位置时。这真的很奇怪,因为执行sql在这两种情况下都返回所有乘客。因此,将结果放回实体时一定会有问题

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();
编辑2 经过一番努力,格特·阿诺德的答案终于找到了! 正如Gert Arnold所建议的,您需要禁用延迟加载并将其关闭。 这将要求对应用程序进行一些额外的更改,因为以前的开发人员喜欢延迟加载-\u-

这项功能现在已经实现。对于早期版本,您需要解决方法(请注意,EF6是早期版本)

实体框架6解决方案 在EF6中,解决方法是首先查询投影(
new
)中所需的对象,然后让关系修复完成它的工作

您可以通过以下方式查询所需的对象:

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();
这里发生的事情是,首先从数据库中获取行驶中的公交车并唤醒乘客从LINQ切换到实体,再切换到LINQ切换到对象,这意味着公交车和乘客将被物化,然后在内存中进行处理。这一点很重要,因为如果没有它,EF将只物化最终投影,
选择(x=>x.b)

现在EF有了这个功能关系修复,它负责设置上下文中具体化的对象之间的所有关联。这意味着,对于每辆
公交车
,现在只装载其唤醒的乘客

当您通过
ToList
获取公交车集合时,您就拥有了包含您想要的乘客的公交车,并且您可以使用AutoMapper对其进行映射

这仅在禁用延迟加载时有效。否则,在转换为DTO期间访问乘客时,EF将延迟加载每辆公交车的所有乘客

禁用延迟加载有两种方法。禁用
LazyLoadingEnabled
将在再次启用时重新激活延迟加载。禁用
ProxyCreationEnabled
将创建自身无法延迟加载的实体,因此在再次启用
ProxyCreationEnabled
后,它们不会启动延迟加载。当上下文的生命周期比单个查询更长时,这可能是最佳选择

但是…多对多

如前所述,此解决方案依赖于关系修复。但是,如所述,关系修复不适用于多对多关联。如果
总线
-
乘客
是多对多,您唯一能做的就是自己修复它:

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();
…整个事情变得更不吸引人了

第三方工具 有一个库,使这变得容易多了。它允许您为实体定义全局筛选器,随后将在查询实体时应用这些筛选器。在您的情况下,这可能类似于:

modelBuilder.Filter("Awake", (Person p) => p.Awake, true);
现在如果你这样做

Context.Busses.Where(b => b.IsDriving)
       .Include(b => b.People)
…您将看到筛选器已应用于包含的集合

您还可以启用/禁用过滤器,这样您就可以控制何时应用它们。我认为这是一个非常整洁的库

AutoMapper制造商提供了一个类似的库:

实体框架核心解决方案 从2.0.0版开始,EF core就有了。这些可用于在要包含的实体上设置预定义的过滤器。当然,这与动态过滤
Include
不具有相同的灵活性。
尽管全局查询筛选器是一个很好的功能,但到目前为止,它的局限性在于筛选器不能包含对导航属性的引用,只能包含对查询根实体的引用。希望在以后的版本中,这些筛选器能够得到更广泛的使用。

免责声明:我是项目的所有者

EF+查询IncludeFilter功能允许筛选相关实体

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

Wiki:

对于任何仍然对此感到好奇的人。EF Core中有内置的功能,可以使用where子句中的.any来完成此操作,因此代码类似于以下内容

<!-- language: language: c# -->

_ctx.Parent
    .Include(t => t.Children)
    .Where(t => t.Children.Any(t => /* Expression here */))

_家长
.包括(t=>t.儿童)
.Where(t=>t.Children.Any(t=>/*表达式在这里*/))

在我的例子中,
包含
是一个
ICollection
,并且也不想返回它们,我只需要获取主e
return await _context.Initiatives
                .Where(x => x.InitiativeYears
                    .Any(y => y.Year == 2020 && y.InitiativeId == x.Id))
                .ToListAsync();
public class Initiative
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<InitiativeYear> InitiativeYears { get; set; }
}

public class InitiativeYear
{
    public int Year { get; set; }
    public int InitiativeId { get; set; }
    public Initiative Initiative { get; set; }
}
var busses = _Context.Busses
                .Include(b => b.Passengers
                                       .Where(p => p.Awake))
            .Where(b => b.IsDriving);