【问题标题】:How do I flatten a hierarchy in LINQ to Entities?如何在 LINQ to Entities 中展平层次结构?
【发布时间】:2012-05-20 08:09:28
【问题描述】:

我有一个类Org,它具有ParentId(指向消费者)和Orgs 属性,以启用组织实例的层次结构。我还有一个类Customer,它有一个OrgId 属性。给定任何名为Owner 的组织实例,我如何检索该组织的所有客户实例?也就是说,在 LINQ 之前,我会以 Owner 为根对 Org 树进行“手动”遍历。不过,我确信存在更简单的东西。

示例:如果我有一个名为“Film”的根级组织,ID 为“1”,子组织名为“Horror”,ParentId 为“1”,ID 为 23,我想查询所有客户在 Film 下,所以我必须让所有客户的 OrgId 都为 1 和 23。

【问题讨论】:

    标签: sql sql-server linq entity-framework linq-to-entities


    【解决方案1】:

    Linq 不会帮助您,但 SQL Server 会。

    创建一个 CTE 以生成一个扁平化的组织 ID 列表,例如:

    CREATE PROCEDURE [dbo].[OrganizationIds]
        @rootId int
    
    AS
        WITH OrgCte AS 
        ( 
            SELECT OrganizationId FROM Organizations where OrganizationId = @rootId
            UNION ALL 
            SELECT parent.OrganizationId FROM Organizations parent
            INNER JOIN OrgCte child ON parent.Parent_OrganizationId = Child.OrganizationId
        ) 
        SELECT * FROM OrgCte 
    
    RETURN 0
    

    现在将函数导入添加到映射到此存储过程的上下文中。这会在您的上下文中产生一个方法(返回的值是可为空的 int,因为原始 Parent_OrganizationId 被声明为 INT NULL):

    public partial class TestEntities : ObjectContext
    {
        public ObjectResult<int?> OrganizationIds(int? rootId)
        {
            ...
    

    现在您可以使用这样的查询:

    // get all org ids for specific root.  This needs to be a separate 
    // query or LtoE throws an exception regarding nullable int.
    var ids = OrganizationIds(2);
    
    // now find all customers
    Customers.Where (c => ids.Contains(c.Organization.OrganizationId)).Dump();
    

    【讨论】:

      【解决方案2】:

      不幸的是,在实体框架中不是原生的。您需要构建自己的解决方案。可能您需要迭代到根目录。您可以通过要求 EF 一次性获得一定数量的父母来优化此算法,如下所示:

      ...
      select new { x.Customer, x.Parent.Customer, x.Parent.Parent.Customer }
      

      使用这种方法,您只能使用静态固定数量的父级(此处为 3),但它会为您节省 2/3 的数据库往返次数。

      编辑:我认为我没有正确理解您的数据模型,但我希望这个想法很清楚。

      编辑 2:为了回应您的评论和编辑,我采用了这样的方法:

      var rootOrg = ...;
      var orgLevels = new [] {
       select o from db.Orgs where o == rootOrg, //level 0
       select o from db.Orgs where o.ParentOrg == rootOrg, //level 1
       select o from db.Orgs where o.ParentOrg.ParentOrg == rootOrg, //level 2
       select o from db.Orgs where o.ParentOrg.ParentOrg.ParentOrg == rootOrg, //level 3
      };
      var setOfAllOrgsInSubtree = orgLevels.Aggregate((a, b) => a.Union(b)); //query for all org levels
      
      var customers = from c in db.Customers where setOfAllOrgsInSubtree.Contains(c.Org) select c;
      

      请注意,这仅适用于有界的最大树深度。在实践中,通常是这种情况(例如 10 或 20)。

      性能不会很好,但它是一个仅限 LINQ-to-Entities 的解决方案。

      【讨论】:

      • 我认为你错过了我的观点。我在示例模型中重命名了一个对象以使事情更清晰。如果一个 Org 是一棵树的 Orgs 的根,我需要获取该 Org 的 Id,以及它下面的树中所有 orgs 的 Id。
      猜你喜欢
      • 2010-12-01
      • 2011-10-02
      • 2010-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-04
      • 2015-08-14
      相关资源
      最近更新 更多