包括在where从句中
本文关键字:where 包括 | 更新日期: 2023-09-27 18:15:57
正如标题所示,我正在寻找一种方法来做一个where子句与include相结合。
以下是我的情况:我负责支持一个充满代码异味的大型应用程序。修改太多代码会导致bug无处不在,所以我正在寻找最安全的解决方案。假设我有一个对象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);
}
}
在这段代码之后,Context被处理,并且在调用方法中产生的总线实体被映射到DTO类(Entity的100%副本)。
这段代码导致多次调用DB,这是一个No-Go,所以我在MSDN博客上找到了这个解决方案
这在调试结果时工作得很好,但是当实体映射到DTO(使用AutoMapper)时,我得到一个异常,即上下文/连接已关闭,对象无法加载。(上下文总是关闭的,不能改变这个:()
所以我需要确保Selected Passengers已经加载(IsLoaded on navigation属性也是False)。如果我检查乘客集合,Count也会抛出异常,但是乘客集合上还有一个名为"包装相关实体"的集合,其中包含我过滤的对象。
是否有办法将这些包装的相关实体加载到整个集合中?(我不能改变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();
Edit2
经过一番努力,答案是格特·阿诺德的作品!正如Gert Arnold建议的那样,你需要禁用惰性加载并保持它关闭。这将要求对应用程序进行一些额外的更改,因为以前的开发人员喜欢延迟加载-_-
此功能现已添加到实体框架核心5。对于较早的版本,您需要一个变通方法(请注意,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();
这里发生的事情是,您首先从数据库中获取正在行驶的公交车并唤醒乘客。然后,AsEnumerable()
从LINQ到实体切换到LINQ到对象,这意味着公共汽车和乘客将被物化,然后在内存中处理。这一点很重要,因为没有它,EF只会实现最终的投影,Select(x => x.b)
,而不是乘客。
现在EF有这个特性关系固定,它负责设置在上下文中物化的对象之间的所有关联。这意味着对于每个Bus
,现在只装载其清醒的乘客。
当你得到ToList
的公共汽车集合时,你就有了你想要的乘客,你可以用AutoMapper映射它们。
这只在惰性加载被禁用时起作用。否则,在转换为dto时,EF将在每个总线访问所有乘客时延迟加载。
有两种方法可以禁用惰性加载。禁用LazyLoadingEnabled
将在再次启用时重新激活延迟加载。禁用ProxyCreationEnabled
将创建无法延迟加载自身的实体,因此它们不会在再次启用ProxyCreationEnabled
后开始延迟加载。当上下文的生存时间比单个查询长时,这可能是最佳选择。
但是…多对多的
如前所述,这种解决方法依赖于关系的修复。然而,正如Slauma在这里解释的那样,关系修复不适用于多对多关联。如果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。过滤器
实体框架核心解决方案
从2.0.0版本开始,EF-core有全局查询过滤器。这些可用于在要包含的实体上设置预定义筛选器。当然,这不能提供与动态过滤Include
相同的灵活性。尽管全局查询过滤器是一个很好的特性,但目前的限制是过滤器不能包含对导航属性的引用,只能包含对查询根实体的引用。希望在以后的版本中,这些过滤器将获得更广泛的使用。
Now EF Core 5.0的Filter Include方法现在支持对包含的实体进行过滤
var busses = _Context.Busses
.Include(b => b.Passengers
.Where(p => p.Awake))
.Where(b => b.IsDriving);
免责声明:我是项目实体框架Plus的所有者
EF+ Query IncludeFilter功能允许过滤相关实体
var buses = Context.Busses
.Where(b => b.IsDriving)
.IncludeFilter(x => x.Passengers.Where(p => p.Awake))
.ToList();
Wiki: EF+ Query IncludeFilter
在我的情况下,Include
是ICollection
,也不想返回它们,我只需要获得主要实体,但被引用的实体过滤。(换句话说,Included
实体),我最终做的是这样的。这将返回Initiatives
但被InitiativeYears
过滤的列表
return await _context.Initiatives
.Where(x => x.InitiativeYears
.Any(y => y.Year == 2020 && y.InitiativeId == x.Id))
.ToListAsync();
这里Initiatives
和InitiativeYears
的关系如下:
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; }
}
对于任何仍然对此感到好奇的人。在EF Core中有这样的内置功能。在where子句中使用。any这样代码就会像这样
_ctx.Parent
.Include(t => t.Children)
.Where(t => t.Children.Any(t => /* Expression here */))