简体   繁体   中英

LINQ to Entity Framework error with nested many-to-many relationships

I have the following object structure (simplified a bit...), where a user is given a role, the role belongs to one or more groups, and each group has a set of privileges (with names). Since a privilege can belong to many groups and a group can contain many roles, the structure is like this:

User -> Role ->> RoleToGroup -> Group - >> GroupToPrivilege -> Privilege(->> = list):

I need to find out if there are any users that has a privilege with the name "name".

I try the following Linq statement, which I thought would give me the correct answer:

DbContext.RootObject.Where (x => x.Role.RoleToGroup.Any(y => y.Group.GroupToPrivilege.Any(z=> z.Privilege.Name == "Name")))

This gives the following sql:

SELECT 
"Extent1"."ROLE_ID" AS "ROLE_ID", 
FROM "USER" "Extent1"
WHERE ( EXISTS (SELECT 
    1 AS "C1"
    FROM ( SELECT 
    "Extent2"."GROUPS_ID" AS "GROUPS_ID"
    FROM "ROLETOGROUP" "Extent2"
    WHERE ("Extent1"."ROLE_ID" = "Extent2"."ROLE_ID")
)  "Project1"
WHERE ( EXISTS (SELECT 
    1 AS "C1"
    FROM  "GROUPTOPRIVILEGE" "Extent3"
    INNER "PRIVILEGE" "Extent4" ON "Extent3"."PRIVILEGES_ID" = "Extent4"."ID"
    WHERE (("Project1"."GROUPS_ID" = "Extent3"."GROUP_ID") AND ('Name' = "Extent4"."NAME"))
    ))
))

When run, I get the following error:

ORA-00904: "Extent1"."ROLE_ID": invalid identifier

If I remove the "Exten1"-part of the query, it runs ok:

SELECT 
"Extent1"."ROLE_ID" AS "ROLE_ID", 
FROM "USER" "Extent1"
WHERE ( EXISTS (SELECT 
1 AS "C1"
FROM ( SELECT 
    "Extent2"."GROUPS_ID" AS "GROUPS_ID"
    FROM "ROLETOGROUP" "Extent2"
    WHERE ("ROLE_ID" = "Extent2"."ROLE_ID")
)  "Project1"
WHERE ( EXISTS (SELECT 
    1 AS "C1"
    FROM  "GROUPTOPRIVILEGE" "Extent3"
    INNER "PRIVILEGE" "Extent4" ON "Extent3"."PRIVILEGES_ID" = "Extent4"."ID"
    WHERE (("Project1"."GROUPS_ID" = "Extent3"."GROUP_ID") AND ('Name' = "Extent4"."NAME"))
    ))
))

But how can I make Entity Framework do the same thing with Linq?

Oracle doesn't correlate the subqueries nested more than one level deep.

https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1853075500346799932

This means that subquery that is nested 2 levels deep cannot reference to top table. This is limitation of Oracle.

Removing "Exten1" part of the query gives you illusion that it works as then "ROLE_ID" refers to the column in the same level subquery (in this case "Extent2"."ROLE_ID" ) and query result will be wrong.

It is quite hard to affect generated SQL in direct way.

Simpliest solution would be to split existing query in multiple queries. First start with getting Privileges, then find matching GroupToPrivilege entries and so on till you get to users.

Another option would be to create stored procedure or view to retrieve necessary data.

If your relationships go in both directions on your model, you could try inverting the way you're navigating your relational structure, so the select is based on Privilege first.

It'll look something like this.

DbContext.Privileges.Where(p => p.Privilege.Name == "Name").SelectMany(p => p.PrivilegeToGroup.Select(pg => pg.Group.GroupToRole.Select(gr => gr.Role.Users)));

on a side note, this looks a lot more friendly if we use fluent LINQ syntax

var query = from p in DbContext.Privileges
            where Privilege.Name == "Name"
            from pg in p.PrivilegeToGroup
            from gr in pg.Group.GroupToRole
            from u in gr.Role.Users
            select u;

You might have to change the property names to match your model (I've only made educated guesses).

This should hopefully eliminate the need for subqueries, although I can't run it and see the SQL that your EF provider will generate. Try it and let me know what results you get.

In response to your comment about the relationships only going one way in the model, you could perform the joins manually. Here's an example to give you an idea of how that would look.

var query = from p in DbContext.Privileges
            where Privilege.Name == "Name"
            join gp in DbContext.GroupToPrivilege
            on p.PrivilegeID equals gp.PrivilegeID
            join rg in DbContext.RoleToGroups
            on gp.GroupID equals rg.GroupID
            join r in DbContext.Role
            on rg.RoleID equals r.RoleID
            join u in DbContext.Users
            on r.RoleID equals u.RoleID
            select u;

The SQL produced by this code will utilise joins instead of subqueries, which will hopefully get around the limitations mentioned in @Kaspars answer.

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