Force inner join with many-to-many relationship en

2019-06-25 22:39发布

问题:

I have a many-to-many relationship setup in my database like so:

User
-------
Id (PK, Identity)
First
Last
...various other fields

Skill
-------
Id (PK, Identity)
Description

UserSkill
-----------
UserId (PK, FK on User.Id)
SkillId (PK, FK On Skill.Id)

When I run this LINQ query on the DbContext:

from u in Users 
from s in u.Skills 
where s.Id == 5 
select new 
{
    u.Id,
    s.Description
})

The SQL generated contains all inner joins which is what I want:

SELECT 
[Extent1].[UserId] AS [UserId], 
[Extent2].[Description] AS [Description]
FROM  [dbo].[UserSkill] AS [Extent1]
INNER JOIN [dbo].[Skill] AS [Extent2] ON [Extent1].[SkillId] = [Extent2].[Id]
WHERE 5 = [Extent2].[Id]

However, when I add a simple extra where clause:

from u in Users 
from s in u.Skills 
where s.Id == 5 
    && u.Last == "test"
select new 
{
    u.Id,
    s.Description
})

The SQL generated now uses a sub-query:

[Extent1].[Id] AS [Id], 
[Filter1].[Description] AS [Description]
FROM  [dbo].[User] AS [Extent1]
INNER JOIN  (SELECT [Extent2].[UserId] AS [UserId], [Extent3].[Description] AS [Description]
    FROM  [dbo].[UserSkill] AS [Extent2]
    INNER JOIN [dbo].[Skill] AS [Extent3] ON [Extent3].[Id] = [Extent2].[SkillId]
    WHERE 5 = [Extent3].[Id] ) AS [Filter1] ON [Extent1].[Id] = [Filter1].[UserId]
WHERE 'test' = [Extent1].[Last]

Maybe I am missing something, but I would think EF would just add another join back to the User table for this query and be able to do a where on User.Last instead of doing a sub-query. Is there any way to force this kind of behavior? Am I doing something wrong?

Thanks.


UPDATE

Cosmin, I am wanting the query to come out like this:

SELECT u.Id, s.Description
FROM [User] u INNER JOIN
        [UserSkill] us ON u.Id = us.UserId INNER JOIN
        [Skill] s ON us.SkillId = s.Id
WHERE s.Id = 2 AND u.Last = 'test'

回答1:

Looks like this is an optimization that EF does not currently do. Personally, I'd stick with the sub query it generates unless performance becomes a problem.

But if you are willing to lose the direct navigation properties for User and Skill, you can model the intermediate table to get the query you are looking for.

public class User
{
    public int Id { get; set; }
    public string First { get; set; }
    public string Last { get; set; }

    public virtual ICollection<UserSkill> UserSkills { get; set; }
}

public class UserSkill
{
    public int Id { get; set; }
    [Required]
    public User User { get; set; }
    [Required]
    public Skill Skill { get; set; }

}

public class Skill
{
    public int Id { get; set; }
    public string Description { get; set; }

    public virtual ICollection<UserSkill> UserSkills { get; set; }
}

Then the following query will produce a join instead of subquery

 from x in db.UserSkills 
 where x.Skill.Id == 5 && x.User.Last == "test"
 select new {x.User.Id, x.Skill.Description};


回答2:

@ryanulit, your issue is valid and it is an issue for all Linq to Entities. Please check the posted MS Forum's URL

MS Forum's URL