正如标题所示,我正在寻找一种方法来将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();
经过多次努力,格特阿诺德的答案奏效了! 正如Gert Arnold建议您需要禁用延迟加载并将其保持关闭状态。 这将要求对应用程序进行一些额外的更改,因为上一代开发人员喜欢Lazy Loading -_-
答案 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.0 的 Filter 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();
答案 3 :(得分:1)
在我的情况下,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; }
}
答案 4 :(得分:-1)
对于仍然对此感到好奇的任何人。在EF Core中有内置的功能可以做到这一点。在where子句内使用.any,因此代码类似于
<!-- language: language: c# -->
_ctx.Parent
.Include(t => t.Children)
.Where(t => t.Children.Any(t => /* Expression here */))