EF:包含 where 子句 [重复]
Posted
技术标签:
【中文标题】EF:包含 where 子句 [重复]【英文标题】:EF: Include with where clause [duplicate] 【发布时间】:2013-05-23 19:44:27 【问题描述】:正如标题所示,我正在寻找一种将 where 子句与包含相结合的方法。
这是我的情况: 我负责支持一个充满代码味道的大型应用程序。 更改太多代码会导致到处出现错误,因此我正在寻找最安全的解决方案。
假设我有一个对象 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,这是不可行的,所以我找到了这个解决方案 ON MSDN Blogs
这在调试结果时效果很好,但是当实体映射到 DTO(使用 AutoMapper)时,我得到一个异常,即上下文/连接已关闭并且无法加载对象。 (上下文总是关闭不能改变这个:()
所以我需要确保 Selected Passengers 已经加载(导航属性上的 IsLoaded 也是 False)。如果我检查Passengers 集合,Count 也会抛出异常,但是在Passegers 集合上还有一个集合,称为“包装的相关实体”,其中包含我过滤的对象。
有没有办法将这些包装的相关实体加载到整个集合中? (我无法更改自动映射器映射配置,因为它在整个应用程序中使用)。
还有其他获取活跃乘客的方法吗?
欢迎任何提示...
编辑
Gert Arnold 的回答不起作用,因为数据没有立即加载。 但是当我简化它并删除它的加载位置时。这真的很奇怪,因为执行 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 建议的那样,您需要禁用延迟加载并保持关闭。 这将要求对应用程序进行一些额外的更改,因为上一个开发人员喜欢延迟加载-_-
【问题讨论】:
这只是一个用 stackoveflow 编写的示例,没有智能感知:p 现在已修复 您能否向我们展示一下该类实现的相关部分对于 Bus、People 和 Passengers 的示例(例如外键和导航属性)? 乘客是导航道具耶 我有点惊讶这个问题几乎没有受到关注,因为考虑到我很难找到它以及它是如何限制 EF 查询数据库的数据量的好方法。人们没有看到 EF 为数据库运行创建的查询吗? @Ellesedil 你的观点是对的,但是那些“长”的 EF 查询对人类来说只是很长的。它们实际上非常有效。您很难编写一个执行计划比 EF 定期生成的查询更快的查询。 【参考方案1】:这个功能现在是added to Entity Framework core 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 to Entities 切换到 LINQ to objects,这意味着公共汽车和乘客将被物化,然后在内存中进行处理。这很重要,因为没有它,EF 只会实现最终投影Select(x => x.b)
,而不是乘客。
现在 EF 具有此功能relationship fixup,它负责设置在上下文中具体化的对象之间的所有关联。这意味着对于每个Bus
,现在只加载其清醒的乘客。
当您获得ToList
收集的公共汽车时,您就有了带有您想要的乘客的公共汽车,您可以使用 AutoMapper 进行映射。
这仅在禁用延迟加载时有效。否则,在转换为 DTO 期间访问乘客时,EF 将延迟加载每辆巴士的所有名乘客。
有两种方法可以禁用延迟加载。禁用LazyLoadingEnabled
将在再次启用时重新激活延迟加载。禁用ProxyCreationEnabled
将创建无法延迟加载的实体它们自己,因此在再次启用ProxyCreationEnabled
后它们不会开始延迟加载。当上下文的寿命比单个查询更长时,这可能是最佳选择。
但是……多对多
如前所述,这种解决方法依赖于关系修复。然而,正如Slauma 所解释的here,关系修复不适用于多对多关联。如果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 拥有global query filters。这些可用于在要包含的实体上设置预定义的过滤器。当然,这并不能提供与即时过滤 Include
相同的灵活性。
尽管全局查询过滤器是一个很棒的功能,但到目前为止,限制是过滤器不能包含对导航属性的引用,只能包含对查询的根实体的引用。希望在以后的版本中,这些过滤器将获得更广泛的使用。
【讨论】:
不幸的是,我现在缺少关于 EntityFrameworkDynamicFilters 的评论不是 DB First 的选项:github.com/zzzprojects/EntityFramework.DynamicFilters/issues/12 感谢@Gert Arnold 的详细阐述!这解决了我的问题,让我再次学习新事物;)! @Gert Arnold,如何在该查询中包含导航属性?假设乘客有一个实体地址作为属性,我如何将它包含到这个查询中?我在 Select 中和 Select 之前没有 .Include 选项,我可以,但这不起作用... @Dimitri 通过嵌套投影:Passengers = b.Passengers.Where(p => p.Awake).Select(p => new p, p.Addresses )
。它一点也不优雅。
@Dimitri 关键部分是执行一个 SQL 查询。任何需要 n+1 查询的东西都会迅速降低性能。执行什么样的内存后处理可能并不重要。【参考方案2】:
免责声明:我是项目的所有者Entity Framework Plus
EF+ Query IncludeFilter 功能允许过滤相关实体。
var buses = Context.Busses
.Where(b => b.IsDriving)
.IncludeFilter(x => x.Passengers.Where(p => p.Awake))
.ToList();
维基:EF+ Query IncludeFilter
【讨论】:
这很好用。但是,如何关闭 IncludeFilter? 目前无法关闭。 它可以工作,但它比包含 AsNotTracking 和不必要的数据要慢。我没有太多数据(48 行对 1 行带有 where 子句)。但是当我使用 IncludeFilter 时,我不能将它与 Include 和 AsNoTracking (您的库限制)混合使用。但我还需要选择 6 个附加对象。在小型测试中,它需要 6 秒对 3 秒。 试试IncludeOptimized
,也许你会有更多机会使用这个。如果您需要其他对象,即使没有过滤器,您仍然可以使用 IncludeFilter
或 IncludeOptimized
【参考方案3】:
对于任何仍然对此感到好奇的人。在 EF Core 中有用于执行此操作的内置功能。在 where 子句中使用 .Any ,因此代码类似于这样的内容
_ctx.Parent
.Include(t => t.Children)
.Where(t => t.Children.Any(t => /* Expression here */))
【讨论】:
我对此进行了测试。不能按您的意愿工作。它所要做的就是排除没有匹配表达式的任何子级的父级。【参考方案4】:在我的情况下,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;
【讨论】:
【参考方案5】:现在EF Core 5.0 的Filter Include 方法现在支持过滤所包含的实体
var busses = _Context.Busses
.Include(b => b.Passengers
.Where(p => p.Awake))
.Where(b => b.IsDriving);
【讨论】:
我用“ToQueryString()”提取了 sql 查询,它不包括“.Where(p => p.Awake)”。真的有用吗?以上是关于EF:包含 where 子句 [重复]的主要内容,如果未能解决你的问题,请参考以下文章