Linq 如何使用实体框架检索父子关系数据并对其进行分页/筛选/排序
我有一个自引用的DB表,用于形成父子关系 身份证件 名称 父ID 1. A. 无效的 2. B 1. 3. C 2. 4. D 2. 5. E 1.Linq 如何使用实体框架检索父子关系数据并对其进行分页/筛选/排序,linq,entity-framework-core,Linq,Entity Framework Core,我有一个自引用的DB表,用于形成父子关系 身份证件 名称 父ID 1. A. 无效的 2. B 1. 3. C 2. 4. D 2. 5. E 1. 这是一个常见的问题,而不是EFCore本机处理的问题 注意事项-您几乎肯定希望添加检查以确保 所有新记录/编辑记录不会无意中创建循环 引用,例如:Temp1=newtemp{Id=1,ParentId=2};Temp2=newtemp{Id=2,ParentId=1}-这将导致加载树时发生不好的事情 首先,不太明显的解决方案是,仅在检索根节点后使用
这是一个常见的问题,而不是EFCore本机处理的问题 注意事项-您几乎肯定希望添加检查以确保 所有新记录/编辑记录不会无意中创建循环 引用,例如:
Temp1=newtemp{Id=1,ParentId=2};Temp2=newtemp{Id=2,ParentId=1}代码>-这将导致加载树时发生不好的事情
首先,不太明显的解决方案是,仅在检索根节点后使用延迟加载:
// Get a list of root nodes.
// Optionally, add pagination, filtering and sorting to the query - .Where(x => x.Name.Contains("blah")).Skip(10).Take(5).OrderBy(x => x.Sequence) etc.
var rootNodes = dbContext.Temp.Where(x => x.parentId == null).ToList();
// Child nodes will be loaded as you access them.
foreach (var node in rootNodes)
{
foreach (var child in node.Children) {
// Do something with children that were lazy-loaded.
}
}
// For a maximum depth of two (or root, leaf, leaf)
// Optionally, add pagination, filtering and sorting to the query - .Where(x => x.Name.Contains("blah")).Skip(10).Take(5).OrderBy(x => x.Sequence) etc.
var query = dbContext.Temp
.Where(x => x.ParentId == null)
.Include(x => x.children)
.ThenInclude(x => x.children);
有关配置延迟加载的详细信息:
注意:延迟加载可能导致效率低下的行为/意外的数据库
查询。这很方便,但方便是有代价的
如果您的业务规则规定了最大深度,则可以按如下所示使用include语句,请注意过滤器仅检索根节点:
// Get a list of root nodes.
// Optionally, add pagination, filtering and sorting to the query - .Where(x => x.Name.Contains("blah")).Skip(10).Take(5).OrderBy(x => x.Sequence) etc.
var rootNodes = dbContext.Temp.Where(x => x.parentId == null).ToList();
// Child nodes will be loaded as you access them.
foreach (var node in rootNodes)
{
foreach (var child in node.Children) {
// Do something with children that were lazy-loaded.
}
}
// For a maximum depth of two (or root, leaf, leaf)
// Optionally, add pagination, filtering and sorting to the query - .Where(x => x.Name.Contains("blah")).Skip(10).Take(5).OrderBy(x => x.Sequence) etc.
var query = dbContext.Temp
.Where(x => x.ParentId == null)
.Include(x => x.children)
.ThenInclude(x => x.children);
对于深度未知的树,最好是获取整个平面列表并自己构建嵌套(客户端而不是EF查询),请注意,分页必须在结果上:
// My recursive function to load child nodes.
Action<List<Temp>, <Temp>> LoadChildren = delegate(sourceList, loadChildrenFor) {
// Get the children for the specified node.
loadChildrenFor.Children = sourceList.Where(x => x.ParentId == loadChildrenFor.Id).ToList();
// Remove the children from the source list AND load its children.
foreach(var node in loadChildrenFor.Children)
{
sourceList.Remove(node); // Don't need in source list any more, it's a child.
LoadChildren(sourceList, node);
}
}
var allNodes = dbContext.Temp.ToList();
foreach (var node in allNodes.Where(x => x.parentId == null))
LoadChildren(allNodes, node);
// Now allNodes ONLY contains root nodes with all their children populated, // Optionally, add pagination, filtering and sorting to the query - .Where(x => x.Name.Contains("blah")).Skip(10).Take(5).OrderBy(x => x.Sequence) etc.
//加载子节点的递归函数。
Action LoadChildren=委托(sourceList,loadChildrenFor){
//获取指定节点的子节点。
loadChildrenFor.Children=sourceList.Where(x=>x.ParentId==loadChildrenFor.Id).ToList();
//从源列表中删除子项并加载其子项。
foreach(loadChildrenFor.Children中的var节点)
{
sourceList.Remove(node);//不再需要在源列表中,它是一个子项。
LoadChildren(sourceList,node);
}
}
var allNodes=dbContext.Temp.ToList();
foreach(allNodes.Where中的var节点(x=>x.parentId==null))
加载子节点(所有节点,节点);
//现在allNodes只包含根节点及其所有子节点,//可选地,向查询添加分页、筛选和排序-.Where(x=>x.Name.contains(“blah”)).Skip(10)、Take(5)、OrderBy(x=>x.Sequence)等。
对于列表展平的一般实现:
如果树非常大且深度未知,并且您只需要一个子集(或一个分支),则递归函数中的多个查询最合适:
// My recursive function to load child nodes.
Action<Temp> LoadChildren = delegate(ofNode) {
ofNode.Children = dbContext.Temp.Where(x => x.ParentId == ofNode.Id).ToList();
foreach(var node in ofNode.Children)
LoadChildren(node);
}
// A list of root nodes I'm interested in - could be any depth in tree.
// Optionally, add pagination and sorting to the query - .Skip(10).Take(5).OrderBy(x => x.Sequence) etc.
var rootNodesImInterestedIn = dbContext.Temp.Where(x => x.Id == 5).ToList();
// Load the children for each root node I'm interested in.
foreach(var node in rootNodesImInterestedIn)
LoadChildren(node);
//加载子节点的递归函数。
Action LoadChildren=委托(ofNode){
ofNode.Children=dbContext.Temp.Where(x=>x.ParentId==ofNode.Id.ToList();
foreach(node.Children中的var节点)
加载子节点;
}
//我感兴趣的根节点列表-可以是树中的任意深度。
//或者,将分页和排序添加到查询-.Skip(10).Take(5).OrderBy(x=>x.Sequence)等。
var rootNodesMinterestedin=dbContext.Temp.Where(x=>x.Id==5.ToList();
//加载我感兴趣的每个根节点的子节点。
foreach(rootNodesMinterestedin中的var节点)
加载子节点;
对于EF,只有在指定大量包含项时才可能:dbContext.Temp.Include(x=>x.Children)。然后Include(x=>x.Children)。然后Include(x=>x.Children)。然后Include(x=>x.Children)实际上,这里最好的解决方案是递归CTE。无论如何,如果有人喜欢杀死数据库,这可能会起作用。@SvyatoslavDanyliv,是的,如果您的数据库支持它,但即使这样,递归CTE仍然会返回一个项目的平面表,您需要将其转换为对象层次结构。另外,这篇文章是关于使用EntityFramework,一种用于.NET开发的ORM工具,而不是关于MS SQL Server(EntityFramework可能使用的许多底层数据库提供者之一)。几乎所有主流数据库都支持这一点(我明白你的意思)。通常,如果这些数据仅用于显示,最好在客户端进行后期处理。我改进了几个用于延迟加载或包含此类任务的项目——它会杀死数据库。通常它是核心功能,如权限、资产等。还请注意,有4种不同的解决方案-只有第一种使用延迟加载(我不推荐,LOL)。我个人更喜欢剩下的解决方案,这取决于表的大小,是否只需要一个子集,等等。当然,很少更改的数据也有缓存的机会。然而,所有这些似乎都不在问题的范围之内。没问题;)EF给了我一份工作,当开发人员放下他们的手。但有时很难理解他们为什么使用这些技术;)