Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query: Related tables sometimes result in incorrect type of join #6844

Closed
arman-g opened this issue Oct 23, 2016 · 2 comments
Closed

Query: Related tables sometimes result in incorrect type of join #6844

arman-g opened this issue Oct 23, 2016 · 2 comments
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@arman-g
Copy link

arman-g commented Oct 23, 2016

Steps to reproduce

Using EF and LINQ to query two tables that are related 1 to 1 or non fails since the sql that is generated by EF is invalid. The UserId is set as PK for both tables "User" and "Suspended". The relation is on UserId field (FK_Suspended_User). Here is a link to the complete description in stack overflow.
img
Here is the code:

 var user = await _db.User
           .Include(x => x.Role)
           .Include(x => x.Suspended)
           .Where(x => x.Email == username & x.Suspended == null)
           .SingleOrDefaultAsync();

The output in sql is:

SELECT [x].[UserId], [x].[Email], [x].[FirstName], [x].[LastName], [x].[Password], [x].[Phone], [x].[RCD], [x].[RoleId], [x].[Salt], [x.Suspended].[UserId], [x.Suspended].[RCD], [x.Suspended].[Reason], [s].[UserId], [s].[RCD], [s].[Reason], [r].[RoleId], [r].[Name]
FROM [User] AS [x]
INNER JOIN [Suspended] AS [x.Suspended] ON [x].[UserId] = [x.Suspended].[UserId]
LEFT JOIN [Suspended] AS [s] ON [s].[UserId] = [x].[UserId]
INNER JOIN [Role] AS [r] ON [x].[RoleId] = [r].[RoleId]

The issue

As you can see the "Suspended" table is joined twice INNER and LEFT. As a result the record is not selected if x.Suspended is null. Also there is no WHERE clause.

Further technical details

"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
"Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.1"
Operating system: Windows 10
Visual Studio version: VS 2015

Project setup: Asp.Net Core Web API

@arman-g
Copy link
Author

arman-g commented Oct 23, 2016

Further Explanation

If we use the code below than the sql output is what we expect.

var user = await _db.User
        .Include(x => x.Role)
        .Include(x => x.Suspended)
        .Where(x => x.Email == username)
        .SingleOrDefaultAsync();

SQL Output

exec sp_executesql N'SELECT TOP(2) [x].[UserId], [x].[Email], [x].[FirstName], [x].[LastName], [x].[Password], [x].[Phone], [x].[RCD], [x].[RoleId], [x].[Salt], [s].[UserId], [s].[RCD], [s].[Reason], [r].[RoleId], [r].[Name]
FROM [User] AS [x]
LEFT JOIN [Suspended] AS [s] ON [s].[UserId] = [x].[UserId]
INNER JOIN [Role] AS [r] ON [x].[RoleId] = [r].[RoleId]
WHERE [x].[Email] = @__username_0',N'@__username_0 varchar(80)',@__username_0='user-email'

Note that the Suspended table is correctly added to the statement as a LEFT JOIN.
Now consider the example below, it just adds a select with a reference to Suspended table in a select section to select a few columns only:

var user = await _db.User
         .Include(x => x.Role)
         .Include(x => x.Suspended)
         .Where(x => x.Email == username)
         .Select(x => new
         {
               x.UserId,
               x.Password,
               x.Salt,
               Role = x.Role.Name,
               x.Suspended
        })
        .SingleOrDefaultAsync();

SQL Output

exec sp_executesql N'SELECT TOP(2) [x.Suspended].[UserId], [x.Suspended].[RCD], [x.Suspended].[Reason], [x].[UserId], [x].[Password], [x].[Salt], [x.Role].[Name]
FROM [User] AS [x]
INNER JOIN [Suspended] AS [x.Suspended] ON [x].[UserId] = [x.Suspended].[UserId]
INNER JOIN [Role] AS [x.Role] ON [x].[RoleId] = [x.Role].[RoleId]
WHERE [x].[Email] = @__username_0',N'@__username_0 varchar(80)',@__username_0='user-email'

What we see here is that the Suspended table is added to the sql as INNER JOIN now. And if Suspended table has no record for the user the statement will not return record.

Conclusion

Referencing a related table (in a where clause or select) changes the LEFT JOIN to INNER JOIN and results no record if the referenced table has no record related to the parent table. This really kills the minimal approach.

@rowanmiller rowanmiller added this to the 1.2.0 milestone Oct 24, 2016
@maumar
Copy link
Contributor

maumar commented Apr 15, 2017

This works in the current bits. We produce the following sql:

SELECT TOP(2) [x].[Id], [x].[Email], [x.Role].[Id], [x.Role].[UserId], [x.Suspended].[Id], [x.Suspended].[UserId]
FROM [Users] AS [x]
LEFT JOIN [Role] AS [x.Role] ON [x].[Id] = [x.Role].[UserId]
LEFT JOIN [Suspended] AS [x.Suspended] ON [x].[Id] = [x.Suspended].[UserId]
WHERE ([x].[Email] = @__username_0) AND [x.Suspended].[Id] IS NULL

@maumar maumar closed this as completed Apr 15, 2017
@maumar maumar added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Apr 15, 2017
@ajcvickers ajcvickers changed the title Invalid sql is generated Query: Related tables sometimes result in incorrect type of join May 9, 2017
@divega divega added closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. and removed closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. labels May 10, 2017
@ajcvickers ajcvickers modified the milestones: 2.0.0-preview1, 2.0.0 Oct 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

5 participants