77

正如标题所示,我正在寻找一种方法来结合包含一个 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,这是不可行的,所以我在MSDN 博客上找到了这个解决方案

这在调试结果时效果很好,但是当实体映射到 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 建议的那样,您需要禁用延迟加载并保持关闭。这将要求对应用程序进行一些额外的更改,因为上一个开发人员喜欢延迟加载 -_-

4

5 回答 5

89

此功能现已添加到 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 具有此功能关系修复,它负责设置在上下文中具体化的对象之间的所有关联。这意味着对于每个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.Filters

实体框架核心解决方法

从 2.0.0 版本开始,EF-core 具有全局查询过滤器。这些可用于在要包含的实体上设置预定义的过滤器。当然,这并不能提供与动态过滤相同的灵活性Include。尽管全局查询过滤器是一个很棒的功能,但到目前为止,限制是过滤器不能包含对导航属性的引用,只能包含对查询的根实体的引用。希望在以后的版本中,这些过滤器将获得更广泛的使用。

于 2013-05-28T20:46:26.203 回答
31

现在 EF Core 5.0Filter Include方法现在支持过滤所包含的实体

var busses = _Context.Busses
                .Include(b => b.Passengers
                                       .Where(p => p.Awake))
            .Where(b => b.IsDriving);
于 2020-12-21T05:54:45.153 回答
27

免责声明:我是Entity Framework Plus项目的所有者

EF+ Query IncludeFilter 功能允许过滤相关实体。

var buses = Context.Busses
                   .Where(b => b.IsDriving)
                   .IncludeFilter(x => x.Passengers.Where(p => p.Awake))
                   .ToList();

Wiki:EF+ 查询包含过滤器

于 2017-03-14T12:05:46.697 回答
2

在我的情况下,它Include是一个ICollection,并且也不想返回它们,我只需要获取主要实体但被引用的实体过滤。(换句话说,Included实体),我最终做的是这个。这将返回Initiatives但过滤的列表InitiativeYears

return await _context.Initiatives
                .Where(x => x.InitiativeYears
                    .Any(y => y.Year == 2020 && y.InitiativeId == x.Id))
                .ToListAsync();

这里 theInitiatives和 theInitiativeYears有如下关系。

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; }
}
于 2020-10-15T20:51:46.933 回答
-2

对于任何仍然对此感到好奇的人。在 EF Core 中有用于执行此操作的内置功能。在 where 子句中使用 .Any ,因此代码类似于这样

_ctx.Parent
    .Include(t => t.Children)
    .Where(t => t.Children.Any(t => /* Expression here */))
于 2020-02-20T13:46:25.160 回答