EF:包含where子句

时间:2013-05-28 18:18:57

标签: c# entity-framework include where-clause

正如标题所示,我正在寻找一种方法来将where子句与include结合使用。

以下是我的情况: 我负责支持一个充满代码味道的大型应用程序。 更改过多代码会导致各处出现错误,因此我正在寻找最安全的解决方案。

假设我有一个对象总线和一个对象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被释放,并且在调用方法中,生成的Bus实体被映射到DTO类(实体的100%副本)。

此代码导致多次调用DB,这是一个禁忌,所以我找到了这个解决方案ON MSDN Blogs

这在调试结果时效果很好,但是当实体映射到DTO(使用AutoMapper)时,我得到一个异常,即Context / Connection已关闭且无法加载对象。 (上下文总是关闭不能改变这个:()

所以我需要确保已经加载了Selected Passengers(导航属性上的IsLoaded也是False)。如果我检查Passengers集合,Count也会抛出Exception,但是在Passegers集合中还有一个名为“包装相关实体”的集合,其中包含我的过滤对象。

有没有办法将这些包装的相关实体加载到整个集合中? (我无法更改automapper mapping配置,因为它在整个应用程序中使用)。

是否有另一种方法可以获得活跃的乘客?

欢迎任何暗示......

修改

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();

EDIT2

经过多次努力,格特阿诺德的答案奏效了! 正如Gert Arnold建议您需要禁用延迟加载并将其保持关闭状态。 这将要求对应用程序进行一些额外的更改,因为上一代开发人员喜欢Lazy Loading -_-

5 个答案:

答案 0 :(得分:50)

您可以通过

查询所需的对象
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具有此功能 relationship fixup ,负责设置在上下文中实现的对象之间的所有关联。这意味着对于每个Bus现在只有其清醒的乘客被装载。

当您通过ToList获得公​​共汽车时,您可以乘坐所需的乘客,并使用AutoMapper进行映射。

仅在禁用延迟加载时才有效。否则,在转换为DTO期间访问乘客时,EF将为每辆公交车延迟所有乘客。

有两种方法可以禁用延迟加载。禁用LazyLoadingEnabled会在再次启用延迟加载时重新激活。禁用ProxyCreationEnabled将创建无法延迟加载自身的实体,因此在再次启用ProxyCreationEnabled后,他们不会开始延迟加载。当上下文比单个查询更长时间时,这可能是最佳选择。

但......多对多

如上所述,这种解决方法依赖于关系修正。但是,正如here所解释的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有global query filters。虽然这是对其功能的一个很好的补充,但到目前为止,限制是过滤器不能包含对导航属性的引用,只能包含对查询的根实体的引用。希望在以后的版本中,这些过滤器将得到更广泛的使用。

过滤包含是一项长期存在的功能请求。可以找到EF核心问题here

答案 1 :(得分:19)

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

var busses = _Context.Busses
                .Include(b => b.Passengers
                                       .Where(p => p.Awake))
            .Where(b => b.IsDriving);

答案 2 :(得分:18)

免责声明:我是该项目的所有者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

答案 3 :(得分:1)

在我的情况下,IncludeICollection,并且也不想返回它们,我只需要获取主要实体,但可以通过引用的实体进行过滤。 (换句话说,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; }
}

答案 4 :(得分:-1)

对于仍然对此感到好奇的任何人。在EF Core中有内置的功能可以做到这一点。在where子句内使用.any,因此代码类似于

<!-- language: language: c# -->

_ctx.Parent
    .Include(t => t.Children)
    .Where(t => t.Children.Any(t => /* Expression here */))