简体   繁体   中英

How do I flatten a hierarchy in LINQ to Entities?

I have a class Org , which has ParentId (which points to a Consumer) and Orgs properties, to enable a hierarchy of Org instances. I also have a class Customer , which has a OrgId property. Given any Org instance, named Owner , how can I retrieve all Customer instances for that org? That is, before LINQ I would do a 'manual' traversal of the Org tree with Owner as its root. I'm sure something simpler exists though.

Example: If I have a root level Org called 'Film', with Id '1', and sub-Org called 'Horror' with ParentId of '1', and Id of 23, I want to query for all Customers under Film, so I must get all customers with OrgId's of both 1 and 23.

Linq won't help you with this but SQL Server will.

Create a CTE to generate a flattened list of Org Ids, something like:

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

Now add a function import to your context mapped to this stored procedure. This results in a method on your context (the returned values are nullable int since the original Parent_OrganizationId is declared as INT NULL):

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

Now you can use a query like this:

// 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();

Unfortunately, not natively in Entity Framework. You need to build your own solution. Probably you need to iterate up to the root. You can optimize this algorithm by asking EF to get a certain number of parents in one go like this:

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

You are limited to a statically fixed number of parent with this approach (here: 3), but it will save you 2/3 of the database roundtrips.

Edit: I think I did not get your data model right but I hope the idea is clear.

Edit 2: In response to your comment and edit I have adapted the approach like this:

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;

Notice that this only works for a bounded maximum tree depth. In practice, this is usually the case (like 10 or 20).

Performance will not be great but it is a LINQ-to-Entities-only solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM