EF:包含where子句

Bee*_*jee 45 c# entity-framework include where-clause

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

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

假设我有一个对象总线和一个对象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);
   }
}
Run Code Online (Sandbox Code Playgroud)

在此代码之后,处理Context,并且在调用方法中,生成的Bus实体被映射到DTO类(实体的100%副本).

此代码导致多次调用DB,这是一个禁止,所以我在MSDN博客上找到了这个解决方案

这在调试结果时效果很好,但是当实体映射到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();
Run Code Online (Sandbox Code Playgroud)

EDIT2

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

Ger*_*old 54

您可以通过查询所需的对象

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();
Run Code Online (Sandbox Code Playgroud)

这里发生的是你首先从数据库中取出驾驶巴士和唤醒乘客.然后,AsEnumerable()从LINQ切换到实体到LINQ到对象,这意味着总线和乘客将被实现,然后在内存中处理.这很重要,因为没有它,EF只会实现最终投影Select(x => x.b),而不是乘客.

现在,EF具有此功能关系修正,负责设置在上下文中实现的对象之间的所有关联.这意味着Bus现在每个人只有清醒的乘客才会被装上.

当您获得公共汽车的集合时,ToList您可以使用您想要的乘客的公共汽车,您可以使用AutoMapper进行映射.

这仅在禁用延迟加载时有效.否则当转换到DTO期间访问乘客时,EF将懒散地加载每辆公共汽车的所有乘客.

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

但是......多对多

如上所述,这种解决方法依赖于关系修正.然而,正如解释在这里通过Slauma,关系修正不能与许多-to-many关联工作.如果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();
Run Code Online (Sandbox Code Playgroud)

......整个事情变得更加吸引人了.

第三方工具

有一个库,EntityFramework.DynamicFilters,使这更容易.它允许您为实体定义全局过滤器,随后将在查询实体时应用这些过滤器.在您的情况下,这可能看起来像:

modelBuilder.Filter("Awake", (Person p) => p.Awake, true);
Run Code Online (Sandbox Code Playgroud)

现在,如果你这样做......

Context.Busses.Where(b => b.IsDriving)
       .Include(b => b.People)
Run Code Online (Sandbox Code Playgroud)

...您将看到过滤器已应用于包含的集合.

您还可以启用/禁用过滤器,以便您可以控制何时应用它们.我认为这是一个非常简洁的图书馆.

AutoMapper的制造商有一个类似的库:EntityFramework.Filters

实体框架核心

从版本2.0.0开始,EF-core具有全局查询过滤器.虽然这是对其功能的一个很好的补充,但到目前为止,限制是过滤器不能包含对导航属性的引用,只能包含对查询的根实体的引用.希望在以后的版本中这些过滤器将获得更广泛的使用.

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


Sat*_*pal 24

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

var busses = _Context.Busses
                .Include(b => b.Passengers
                                       .Where(p => p.Awake))
            .Where(b => b.IsDriving);
Run Code Online (Sandbox Code Playgroud)

  • 我用“ToQueryString()”提取了sql查询,但它不包括“.Where(p => p.Awake)”。它真的有效吗? (4认同)

Jon*_*nan 21

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

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

var buses = Context.Busses
                   .Where(b => b.IsDriving)
                   .IncludeFilter(x => x.Passengers.Where(p => p.Awake))
                   .ToList();
Run Code Online (Sandbox Code Playgroud)

Wiki:EF + Query IncludeFilter