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,也许你会有更多机会使用这个。如果您需要其他对象,即使没有过滤器,您仍然可以使用 IncludeFilterIncludeOptimized【参考方案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();

这里InitiativesInitiativeYears有如下关系。

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 子句 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

EF4 将 is null 子句添加到 where 子句

EF 6 - 注入where子句

EF6 - 在 Where() 子句中使用 await 关键字

EF Core 在复杂的 Where 子句上声明多个变量

EF Core 复杂的 where 子句

EF 核心 6 选择空值,尽管 where 子句要求不为空