在 Linq 中使用包含 Intersect/Union/Exclude
Using Include with Intersect/Union/Exclude in Linq
本来应该是一个相对简单的任务,却变成了一个令人惊讶的复杂问题。以至于我开始认为我的方法可能只是超出了 Linq 的功能范围。
我想做的是拼凑一个 Linq 查询,然后调用 .Include()
以从多个子实体中提取值。例如,假设我有这些实体:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public ISet<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
public string Name { get; set; }
}
假设我想执行查询以从 Parent
检索记录,其中 Name
是某个值,Location
是其他值,然后包括 Child
也有记录。但无论出于何种原因,我不知道 Name
和 Location
同时的查询值,所以我必须采用两个单独的可查询对象并加入它们,例如:
MyDbContext C = new MyDbContext();
var queryOne = C.Parent.Where(p => p.Name == myName);
var queryTwo = C.Parent.Where(p => p.Location == myLocation);
var finalQuery = queryOne.Intersect(queryTwo);
效果很好,产生的结果与我刚刚完成的完全一样:
var query = C.Parent.Where(p => p.Name == myName && p.Location = myLocation);
同样,我可以:
var finalQuery = queryOne.Union(queryTwo);
给我结果就像我有:
var query = C.Parent.Where(p => p.Name == myName || p.Location = myLocation);
但是,一旦应用 Intersect()
或 Union()
,我无法做的就是使用 Include()
映射 Child
,如:
finalQuery.Include(p => p.Children);
此代码将编译,但产生如下结果:
- 在
Union()
的情况下,将生成结果集,但不会枚举 Child
个实体。
- 在
Intersect()
的情况下,尝试应用 Include()
时会生成 运行 时间错误,如下所示:
Expression of type
'System.Collections.Generic.IEnumerable`1[Microsoft.EntityFrameworkCore.Query.Internal.AnonymousObject]'
cannot be used for parameter of type
'System.Collections.Generic.IEnumerable`1[System.Object]' of method
'System.Collections.Generic.IEnumerable`1[System.Object]
Intersect[Object](System.Collections.Generic.IEnumerable`1[System.Object],
System.Collections.Generic.IEnumerable`1[System.Object])'
令我困惑的是这段代码将完全按预期工作:
var query = C.Parent.Where(p => p.Name == myName).Where(p => p.Location == myLocation);
query.Include(p => p.Children);
即,根据需要的结果,包括 Child
个枚举的实体。
my methodology perhaps is simply out of scope with the capabilities of Linq
问题不是 LINQ,而是 EF Core 查询翻译,特别是缺少 Intersect
/ Union
/ Concat
/ Except
方法 SQL 翻译,由 #6812 Query: Translate IQueryable.Concat/Union/Intersect/Except/etc. to server 跟踪。
很快,此类查询目前在您的案例 #1 中使用 client evaluation, which with combination of how the EF Core handles Include
leads to many unexpected runtime exceptions (like your case #2) or wrong behaviors (like Ignored Includes。
因此,根据 EF Core 团队负责人的回应,虽然您的方法在技术上完全合理
Changing this to producing a single SQL query on the server isn't currently a top priority
所以目前甚至没有计划在 3.0 版本中发布,尽管有计划更改(重写)整个查询翻译管道,这也可能允许实现它。
目前,您别无选择。您可以尝试自己处理查询表达式树,但这是一项复杂的任务,您可能会发现它尚未实现的原因:) 如果您可以将查询转换为具有组合 Where
条件的等效单个查询,那么申请 Include
就可以了。
P.S。请注意,即使现在您的方法在技术上 "works" w/o Include
,在客户端评估它的方式方面,它绝对不等同于相应的单个查询。
很长一段时间过去了,但是这个 .Include
问题在 EF 6 中仍然存在。但是,有一个解决方法:在 intersecting/Unionizing 之前附加每个子请求 .Include
。
MyDbContext C = new MyDbContext();
var queryOne = db.Parents.Where(p => p.Name == parent.Name).Include("Children");
var queryTwo = db.Parents.Where(p => p.Location == parent.Location).Include("Children");
var finalQuery = queryOne.Intersect(queryTwo);
正如@Ivan Stoev 所述,Intersection/Union 是用后取数据完成的,而 .Include
在请求时是可以的。
所以,截至目前,您可以使用这一选项。
本来应该是一个相对简单的任务,却变成了一个令人惊讶的复杂问题。以至于我开始认为我的方法可能只是超出了 Linq 的功能范围。
我想做的是拼凑一个 Linq 查询,然后调用 .Include()
以从多个子实体中提取值。例如,假设我有这些实体:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public ISet<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
public string Name { get; set; }
}
假设我想执行查询以从 Parent
检索记录,其中 Name
是某个值,Location
是其他值,然后包括 Child
也有记录。但无论出于何种原因,我不知道 Name
和 Location
同时的查询值,所以我必须采用两个单独的可查询对象并加入它们,例如:
MyDbContext C = new MyDbContext();
var queryOne = C.Parent.Where(p => p.Name == myName);
var queryTwo = C.Parent.Where(p => p.Location == myLocation);
var finalQuery = queryOne.Intersect(queryTwo);
效果很好,产生的结果与我刚刚完成的完全一样:
var query = C.Parent.Where(p => p.Name == myName && p.Location = myLocation);
同样,我可以:
var finalQuery = queryOne.Union(queryTwo);
给我结果就像我有:
var query = C.Parent.Where(p => p.Name == myName || p.Location = myLocation);
但是,一旦应用 Intersect()
或 Union()
,我无法做的就是使用 Include()
映射 Child
,如:
finalQuery.Include(p => p.Children);
此代码将编译,但产生如下结果:
- 在
Union()
的情况下,将生成结果集,但不会枚举Child
个实体。 - 在
Intersect()
的情况下,尝试应用Include()
时会生成 运行 时间错误,如下所示:
Expression of type 'System.Collections.Generic.IEnumerable`1[Microsoft.EntityFrameworkCore.Query.Internal.AnonymousObject]' cannot be used for parameter of type 'System.Collections.Generic.IEnumerable`1[System.Object]' of method 'System.Collections.Generic.IEnumerable`1[System.Object] Intersect[Object](System.Collections.Generic.IEnumerable`1[System.Object], System.Collections.Generic.IEnumerable`1[System.Object])'
令我困惑的是这段代码将完全按预期工作:
var query = C.Parent.Where(p => p.Name == myName).Where(p => p.Location == myLocation);
query.Include(p => p.Children);
即,根据需要的结果,包括 Child
个枚举的实体。
my methodology perhaps is simply out of scope with the capabilities of Linq
问题不是 LINQ,而是 EF Core 查询翻译,特别是缺少 Intersect
/ Union
/ Concat
/ Except
方法 SQL 翻译,由 #6812 Query: Translate IQueryable.Concat/Union/Intersect/Except/etc. to server 跟踪。
很快,此类查询目前在您的案例 #1 中使用 client evaluation, which with combination of how the EF Core handles Include
leads to many unexpected runtime exceptions (like your case #2) or wrong behaviors (like Ignored Includes。
因此,根据 EF Core 团队负责人的回应,虽然您的方法在技术上完全合理
Changing this to producing a single SQL query on the server isn't currently a top priority
所以目前甚至没有计划在 3.0 版本中发布,尽管有计划更改(重写)整个查询翻译管道,这也可能允许实现它。
目前,您别无选择。您可以尝试自己处理查询表达式树,但这是一项复杂的任务,您可能会发现它尚未实现的原因:) 如果您可以将查询转换为具有组合 Where
条件的等效单个查询,那么申请 Include
就可以了。
P.S。请注意,即使现在您的方法在技术上 "works" w/o Include
,在客户端评估它的方式方面,它绝对不等同于相应的单个查询。
很长一段时间过去了,但是这个 .Include
问题在 EF 6 中仍然存在。但是,有一个解决方法:在 intersecting/Unionizing 之前附加每个子请求 .Include
。
MyDbContext C = new MyDbContext();
var queryOne = db.Parents.Where(p => p.Name == parent.Name).Include("Children");
var queryTwo = db.Parents.Where(p => p.Location == parent.Location).Include("Children");
var finalQuery = queryOne.Intersect(queryTwo);
正如@Ivan Stoev 所述,Intersection/Union 是用后取数据完成的,而 .Include
在请求时是可以的。
所以,截至目前,您可以使用这一选项。