C# EF:包含where子句
正如标题所示,我正在寻找一种结合include的where子句的方法 以下是我的情况: 我负责支持一个充满代码气味的大型应用程序。 更改太多代码会导致到处都有bug,所以我正在寻找最安全的解决方案 假设我有一个对象总线和一个对象人物(总线有一个导航道具人物集合)。 在我的查询中,我需要选择所有公交车,其中只有醒着的乘客。这是一个过于简单的虚拟示例 在当前代码中:C# EF:包含where子句,c#,entity-framework,include,where-clause,C#,Entity Framework,Include,Where Clause,正如标题所示,我正在寻找一种结合include的where子句的方法 以下是我的情况: 我负责支持一个充满代码气味的大型应用程序。 更改太多代码会导致到处都有bug,所以我正在寻找最安全的解决方案 假设我有一个对象总线和一个对象人物(总线有一个导航道具人物集合)。 在我的查询中,我需要选择所有公交车,其中只有醒着的乘客。这是一个过于简单的虚拟示例 在当前代码中: var busses = Context.Busses.Where(b=>b.IsDriving == true); forea
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的多次调用,这是不可能的,所以我找到了这个解决方案
这在调试结果时非常有效,但是当实体映射到DTO(使用AutoMapper)时,我得到一个异常,即上下文/连接已关闭,无法加载对象。(上下文始终关闭无法更改此:()
因此,我需要确保已加载所选乘客(IsLoaded on navigation属性也为False)。如果我检查乘客集合,计数也会引发异常,但在名为“包装相关实体”(wrapped related entities)的乘客集合中也有一个集合,其中包含我的筛选对象
有没有办法将这些包装好的相关实体加载到整个集合中?
(我无法更改automapper映射配置,因为它在整个应用程序中使用)
有没有其他办法让活跃的乘客上车
欢迎任何提示
编辑
格特·阿诺德的答案不起作用,因为数据没有被急切地加载。
但是当我简化它并删除加载它的位置时。这真的很奇怪,因为执行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所建议的,您需要禁用延迟加载并将其关闭。
这将要求对应用程序进行一些额外的更改,因为以前的开发人员喜欢延迟加载-\u-这项功能现在已经实现。对于早期版本,您需要解决方法(请注意,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();
这里发生的事情是,首先从数据库中获取行驶中的公交车并唤醒乘客从LINQ切换到实体,再切换到LINQ切换到对象,这意味着公交车和乘客将被物化,然后在内存中进行处理。这一点很重要,因为如果没有它,EF将只物化最终投影,选择(x=>x.b)
现在EF有了这个功能关系修复,它负责设置上下文中具体化的对象之间的所有关联。这意味着,对于每辆公交车
,现在只装载其唤醒的乘客
当您通过ToList
获取公交车集合时,您就拥有了包含您想要的乘客的公交车,并且您可以使用AutoMapper对其进行映射
这仅在禁用延迟加载时有效。否则,在转换为DTO期间访问乘客时,EF将延迟加载每辆公交车的所有乘客
禁用延迟加载有两种方法。禁用LazyLoadingEnabled
将在再次启用时重新激活延迟加载。禁用ProxyCreationEnabled
将创建自身无法延迟加载的实体,因此在再次启用ProxyCreationEnabled
后,它们不会启动延迟加载。当上下文的生命周期比单个查询更长时,这可能是最佳选择
但是…多对多
如前所述,此解决方案依赖于关系修复。但是,如所述,关系修复不适用于多对多关联。如果总线
-乘客
是多对多,您唯一能做的就是自己修复它:
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();
…整个事情变得更不吸引人了
第三方工具
有一个库,使这变得容易多了。它允许您为实体定义全局筛选器,随后将在查询实体时应用这些筛选器。在您的情况下,这可能类似于:
modelBuilder.Filter("Awake", (Person p) => p.Awake, true);
现在如果你这样做
Context.Busses.Where(b => b.IsDriving)
.Include(b => b.People)
…您将看到筛选器已应用于包含的集合
您还可以启用/禁用过滤器,这样您就可以控制何时应用它们。我认为这是一个非常整洁的库
AutoMapper制造商提供了一个类似的库:
实体框架核心解决方案
从2.0.0版开始,EF core就有了。这些可用于在要包含的实体上设置预定义的过滤器。当然,这与动态过滤Include
不具有相同的灵活性。
尽管全局查询筛选器是一个很好的功能,但到目前为止,它的局限性在于筛选器不能包含对导航属性的引用,只能包含对查询根实体的引用。希望在以后的版本中,这些筛选器能够得到更广泛的使用。免责声明:我是项目的所有者 EF+查询IncludeFilter功能允许筛选相关实体
var buses = Context.Busses
.Where(b => b.IsDriving)
.IncludeFilter(x => x.Passengers.Where(p => p.Awake))
.ToList();
Wiki:对于任何仍然对此感到好奇的人。EF Core中有内置的功能,可以使用where子句中的.any来完成此操作,因此代码类似于以下内容
<!-- language: language: c# -->
_ctx.Parent
.Include(t => t.Children)
.Where(t => t.Children.Any(t => /* Expression here */))
_家长
.包括(t=>t.儿童)
.Where(t=>t.Children.Any(t=>/*表达式在这里*/))
在我的例子中,包含
是一个ICollection
,并且也不想返回它们,我只需要获取主e
return await _context.Initiatives
.Where(x => x.InitiativeYears
.Any(y => y.Year == 2020 && y.InitiativeId == x.Id))
.ToListAsync();
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; }
}
var busses = _Context.Busses
.Include(b => b.Passengers
.Where(p => p.Awake))
.Where(b => b.IsDriving);