一尘不染

EF:包含在where子句中

c#

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

这是我的情况:我负责支持充满代码味道的大型应用程序。更改过多的代码会导致到处都有错误,因此我正在寻找最安全的解决方案。

假设我有一个对象Bus和一个对象People(Bus有一个导航道具Collection of
People)。在我的查询中,我需要选择所有只有醒着的乘客的公共汽车。这是一个简单的虚拟示例

在当前代码中:

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的多次调用,这是不可行的,因此我在MSDN博客上找到了此解决方案。

这在调试结果时效果很好,但是当实体映射到DTO(使用AutoMapper)时,出现了一个异常,即上下文/连接已关闭并且无法加载该对象。(上下文始终处于关闭状态,无法更改此:()

因此,我需要确保已加载“所选乘客”(导航属性上的“
IsLoaded”也为False)。如果检查乘客集合,则Count也会引发异常,但在Passegers集合上还有一个名为“包装相关实体”的集合,其中包含我的过滤对象。

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

还有另一种方式获得活跃乘客吗?

欢迎任何提示…

编辑

Gert Arnold的答案不起作用,因为数据没有急切加载。但是当我简化它并删除它的加载位置时。这真的很奇怪,因为在两种情况下execute
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的工作得到了答案!正如Gert
Arnold所建议的那样,您需要禁用“延迟加载”并将其保持关闭状态。由于先前的开发人员喜欢延迟加载-_-,因此这将要求对应用程序进行一些其他更改。


阅读 575

收藏
2020-05-19

共1个答案

一尘不染

最新消息 :此功能现已添加到Entity
Framework核心中

您可以通过查询以下对象

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

此处发生的情况是,您首先获取了驾驶巴士并从数据库中唤醒了乘客。然后,AsEnumerable()从LINQ切换到Entities,再从LINQ切换到对象,这意味着将实现公共汽车和乘客,然后在内存中对其进行处理。这一点很重要,因为如果没有它,EF只会实现最终的投影Select(x => x.b),而不是乘客。

现在,EF具有此功能 关系修补程序 ,可以解决在上下文中实现的对象之间设置所有关联的问题。这意味着Bus现在每个乘客都只被唤醒。

领取ToList公交车时,您便有了想要的乘客的公交车,可以使用AutoMapper对其进行映射。

这仅在禁用延迟加载时有效。否则,在转换为DTO的过程中访问EF时,EF将为每辆巴士延迟加载 所有 乘客。

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

但是…多对多

如前所述,此变通办法依赖于关系修复。然而,正如解释在这里通过Slauma,关系修正不能与许多-
to-many关联工作。如果Bus- Passenger是多对多,则您唯一可以做的就是自己修复它:

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

……整个事情变得越来越没有吸引力了。

第三方工具

有一个库EntityFramework.DynamicFilters使它变得容易得多。它允许您为实体定义全局过滤器,随后将在查询实体时将其应用。在您的情况下,可能看起来像:

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

现在,如果您愿意…

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

…您将看到过滤器已应用于包含的集合。

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

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

实体框架核心

从2.0.0版开始,EF-core具有全局查询过滤器。尽管这是对其功能的重要补充,但到目前为止的限制是,过滤器不能包含对导航属性的引用,而不能包含对查询的根实体的引用。希望在更高版本中,这些过滤器将获得更广泛的使用。

过滤的内容是一项长期存在的功能要求。EF核心问题可在此处找到。

2020-05-19