c# - 如何过滤相同类型的嵌套实体?
问题描述
以下是我的实际情况的简化版。假设我有这个Person
实体:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
// etc.
// Some navigation properties
public virtual ICollection<Thing> Things { get; set; }
// etc.
}
我编写了一个扩展方法来基于一个或多个属性进行过滤:
public static IQueryable<Person> Filter(this IQueryable<Person> query,
string name = null, int? thingId = null,
Foo etc = null)
{
if (!string.IsNullOrEmpty(name))
query = query.Where(p => p.Name.ToLower().Contains(name.ToLower()));
if (thingId.HasValue)
query = query.Where(p => p.Things.Count > 0 &&
p.Things.Any(t => t.Id == thingId.Value));
// etc.
return query;
}
..我可以这样使用:
var query = context.People.Filter(name, thingId);
var filteredPeople = query.Include(p => p.Things).Include(__).OrderBy(__).ToList();
我想做Person
一个嵌套实体(即,每个人都有一组人)。因此,我添加了以下属性:
public virtual ICollection<Person> Children { get; set; }
[ForeignKey("Parent")]
public int? ParentId { get; set; }
public virtual Person Parent { get; set; }
现在我正在努力实现过滤逻辑。我需要的是:
- 如果父项
Person
与过滤器匹配或其后代之一匹配,则将包括父项。 Person
只有符合上述条件的儿童才被纳入。
对于第二个问题,我可能会尝试这个答案中的解决方案,但我需要先解决第一个问题。我试图创建一个递归表达式,如下所示:
private static IQueryable<Person> FilterByName(this IQueryable<Person> query, string name)
{
if (string.IsNullOrEmpty(name)) return query;
Expression<Func<Person, bool>> selector = (p) =>
p.Name.ToLower().Contains(name.ToLower())
|| p.Children.AsQueryable().FilterByName(name).Any();
return query.Where(selector);
}
..但我得到一个例外,说它“不能翻译成商店表达式”。
我能想到的唯一其他解决方案是递归迭代子树并尝试手动构建列表,这是低效的,因为它需要太多查询。
如何有效地过滤集合Person
及其后代?
解决方案
我们需要创建一个实现以下功能的查询:
如果父 Person 匹配过滤器或其后代之一匹配,则将包含它。
仅当满足上述条件时才包含子 Person。
如果我们颠倒我们的思维方式,并意识到没有父母或孩子实体,只有人,我们可以重写我们的标准,说如下:
- 如果一个人匹配过滤器,我们想要包括那个人和他的所有祖先。
这极大地简化了我们需要执行的查询并给出相同的结果。
现在,正如一些评论所指出的,就分层查询而言,Entity Framework 并没有内置很多内容。这并不意味着我们不能使用 EF,但我们确实需要编写和执行原始 SQL 查询。
如果您想将实体保存到数据库,EF 仍会处理对象映射和更改跟踪。
这没有能力被 Linq 进一步扩展,例如.Where()
它.Include()
是一个不可堆肥的查询。
public static IEnumerable<Person> FilterPeople(this DbSet<Person> people, string name)
{
return people.FromSqlRaw(
"WITH child AS (" +
" SELECT * FROM People" +
" WHERE Name LIKE {0}" +
" UNION ALL" +
" SELECT parent.* FROM People parent" +
" INNER JOIN child ON parent.Id = child.ParentId" +
") SELECT * FROM child",
$"%{name}%")
.AsEnumerable();
}
这转化为参数化查询:
exec sp_executesql N'WITH child AS (SELECT * FROM People WHERE Name LIKE @p0 UNION ALL SELECT parent.* FROM People parent INNER JOIN child ON parent.Id = child.ParentId) SELECT * FROM child',N'@p0 nvarchar(4000)',
@p0=N'%Dick%'
推荐阅读
- javascript - 引用者未在 GET 请求中发送,但在 Ionic/Capacitor 应用程序的 POST 中发送
- c++ - 为什么静态成员和静态常量成员在初始化时不一样?
- python - 未来在 python 中的使用
- node.js - 如何重新启动 Node.js 应用程序并将新进程移交给控制台
- c# - Azure Functions 的每个环境配置?
- 3d - rgl:如何避免透明 3D 椭球中的莫尔效应?
- python - 如何在任何其他 IDE(如 vs_code)上打开 jupyter 项目
- python - 如何从 Python 对象中提取内存地址
- javascript - 为 youtube 视频添加华丽的弹出窗口
- python - CMAKE 找不到 PythonLibs(缺少:PYTHON_INCLUDE_DIRS)