【问题标题】:Recursive linq expressions to get non NULL parent value?递归 linq 表达式以获取非 NULL 父值?
【发布时间】: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() 的底层函数是原因。下面的解决方案有效

标签: linq recursion entity-framework-core


【解决方案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 的递归访问。尽管您可能希望在后台运行维护工作,以定期检查最近修改的数据,以确保其面包屑跟踪准确,并在有任何损坏时提醒您。 (通过错误代码等)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-26
    • 1970-01-01
    • 1970-01-01
    • 2022-01-20
    • 1970-01-01
    • 2023-03-22
    相关资源
    最近更新 更多