如标题所示,我正在寻找一种将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();
经过大量的努力,Gert Arnold的工作得到了答案!正如Gert Arnold所建议的那样,您需要禁用“延迟加载”并将其保持关闭状态。由于先前的开发人员喜欢延迟加载-_-,因此这将要求对应用程序进行一些其他更改。
最新消息 :此功能现已添加到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),而不是乘客。
AsEnumerable()
Select(x => x.b)
现在,EF具有此功能 关系修补程序 ,可以解决在上下文中实现的对象之间设置所有关联的问题。这意味着Bus现在每个乘客都只被唤醒。
Bus
领取ToList公交车时,您便有了想要的乘客的公交车,可以使用AutoMapper对其进行映射。
ToList
这仅在禁用延迟加载时有效。否则,在转换为DTO的过程中访问EF时,EF将为每辆巴士延迟加载 所有 乘客。
有两种方法可以禁用延迟加载。LazyLoadingEnabled再次启用时,禁用将重新激活延迟加载。禁用ProxyCreationEnabled将创建无法 自行 延迟加载的实体,因此ProxyCreationEnabled再次启用后它们将不会开始延迟加载。当上下文的寿命比单个查询更长时,这可能是最佳选择。
LazyLoadingEnabled
ProxyCreationEnabled
但是…多对多
如前所述,此变通办法依赖于关系修复。然而,正如解释在这里通过Slauma,关系修正不能与许多- to-many关联工作。如果Bus- Passenger是多对多,则您唯一可以做的就是自己修复它:
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核心问题可在此处找到。