递归 linq 表达式以获取非 NULL 父值?

Posted

技术标签:

【中文标题】递归 linq 表达式以获取非 NULL 父值?【英文标题】:Recursive linq expressions to get non NULL parent value? 【发布时间】:2022-01-13 19:22:31 【问题描述】:

我编写了一个简单的递归函数来爬上具有 ID 和 PARENTID 的表的树。

但是当我这样做时,我得到了这个错误

System.InvalidOperationException:“无法跟踪实体类型“InternalOrg”的实例,因为已经在跟踪具有相同键值 'Id' 的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。

是否有另一种方法可以做到这一点,或者可以在一个 LINQ 表达式中完成?

private InternalOrgDto GetInternalOrgDto(DepartmentChildDto dcDto)

    if (dcDto.InternalOrgId != null)
    
        InternalOrg io = _internalOrgRepo.Get(Convert.ToInt32(dcDto.InternalOrgId));
        InternalOrgDto ioDto = new InternalOrgDto
        
            Id = io.Id,
            Abbreviation = io.Abbreviation,
            Code = io.Code,
            Description = io.Description
        ;

        return ioDto;
    
    else
    
        //manually get parent department
        Department parentDepartment = _departmentRepo.Get(Convert.ToInt32(dcDto.ParentDepartmentId));
        DepartmentChildDto parentDepartmenDto = ObjectMapper.Map<DepartmentChildDto>(parentDepartment);

        return GetInternalOrgDto(parentDepartmenDto);
    

【问题讨论】:

显示调用堆栈。 Bug 绝对不在这个函数中。这可能是由AttachUpdateAdd 引起的。 递归调用 .get() 的底层函数是原因。下面的解决方案有效 【参考方案1】:

有没有办法通过 Linq 从给定的孩子那里获得***父母?不是我知道的。您可以像您所做的那样递归地执行此操作,但我建议简化查询以避免加载整个实体,直到您获得所需的内容。我从您的代码中猜测只有***父部门才有 InternalOrg?否则,此方法将递归父母,直到找到一个。这可以加快速度:

private InternalOrgDto GetInternalOrgDto(DepartmentChildDto dcDto)

    var internalOrgid = dcDto.InternalOrgId 
        ?? FindInternalOrgid(dcDto.ParentDepartmentId) 
        ?? throw new InternalOrgNotFoundException();

    InternalOrgDto ioDto = _context.InternalOrganizations
        .Where(x => x.InternalOrgId == internalOrgId.Value)
        .Select(x => new InternalOrgDto
        
            Id = x.Id,
            Abbreviation = x.Abbreviation,
            Code = x.Code,
            Description = x.Description
        ).Single();
    return ioDto;


private int? FindInternalOrgid(int? departmentId)

    if (!departmentId.HasValue)
       return (int?) null;

    var details = _context.Departments
        .Where(x => x.DepartmentId == departmentId.Value)
        .Select(x => new 
         
            x.InternalOrgId,
            x.ParentDepartmentId
        ).Single();
     if (details.InternalOrgId.HasValue)
         return details.InternalOrgId;

     return findInternalOrgId(details.parentDepartmentId);

这里的主要考虑因素是避免返回实体或实体集的存储库方法,尤其是在您不需要关于实体的所有信息的情况下。通过利用 EF 通过 Linq 提供的 IQueryable,我们可以仅投影到我们需要的数据,而不是返回每个字段。数据库服务器可以通过索引更好地适应这一点,并帮助避免锁定之类的事情。如果您使用存储库来强制执行低级域规则或启用单元测试,则存储库可以公开 IQueryable&lt;TEntity&gt; 而不是 IEnumerable&lt;TEntity&gt; 甚至 TEntity 以启用投影和其他 EF Linq 优点。

另一个选项考虑我在哪里有层次数据,其中关系很重要,我想快速找到与父级相关的所有实体,或者到达特定级别,一个选项是存储每条更新记录的面包屑如果该项目曾经被移动过。好处是这些检查变得非常简单,风险是任何地方/任何可以修改数据关系的东西都可能使面包屑路径处于无效状态。

例如,如果我的部门 ID 22 属于部门 8,而部门 2 属于***部门,则 22 的面包屑路径将是:“2,8”。如果面包屑为空,则我们有一个***实体。 (并且没有父 ID)我们可以使用简单的string.Split() 操作来解析面包屑。这完全避免了对 DB 的递归访问。尽管您可能希望在后台运行维护工作,以定期检查最近修改的数据,以确保其面包屑跟踪准确,并在有任何损坏时提醒您。 (通过错误代码等)

【讨论】:

以上是关于递归 linq 表达式以获取非 NULL 父值?的主要内容,如果未能解决你的问题,请参考以下文章

SQL Recursive:获取孩子的父值

TSQL函数和Check约束以确保表中存在父值

Linq-to-Sql:递归获取孩子

单链表的反转非递归算法

linq 在查询表达式中处理 null 值

MariaDB表表达式:CTE