Description
rosieks created an issue — 29th June 2012, 13:39:34:
When I run query like this:
session.Query().Fetch(c => c.Addresses).Take(10);
or
session.Query().Take(10).Fetch(c => c.Addresses);
there is generated wrong SQL, because paged is query with join - this is wrong.
Alexander Zaytsev added a comment — 29th June 2012, 13:47:55:
I believe that this might be not an issue, because you should not mix paging and fetching.
Oskar Berggren added a comment — 29th June 2012, 13:50:34:
Fetching implies a join, which makes paging work differently. Use a subquery to for paging and fetching in the other query.
rosieks added a comment — 29th June 2012, 16:56:51:
I would like to use subquery. Unfortunately it doesn't work for me in LINQ, so there is no way to avoid N+1.
I don't understand why this behavior is accepted. I want take 10 customers, not 10 records from DB. LINQ is not SQL. It is possible to write correct query. You should make paging before join. In Entity Framework Include method work properly.
Michael Teper added a comment — 2nd July 2012, 17:59:25:
I am with Slawomir -- while I appreciate the underlying mechanics, the behavior is unexpected and I'd argue broken.
Oskar Berggren added a comment — 2nd July 2012, 18:26:40:
I'll reopen for now, in case anyone wants to work on this. Supposedly an improvement in this area would feature an automatic rewrite to use a subquery. Is this what EF does?
Slawomir, why can't you use a subquery? And remember you also have the batch-size in mappings to play with to avoid N+1.
rosieks added a comment — 2nd July 2012, 19:15:41:
EF generate the following query:
SELECT <Project1>.[Id] AS [Id], [Project1].[C1] AS [C1], [Project1].[Id1] AS [Id1], [Project1].[Customer*Id] AS [Customer*Id] FROM ( SELECT <Limit1>.[Id] AS [Id], [Extent2].[Id] AS [Id1], [Extent2].[Customer*Id] AS [Customer*Id], CASE WHEN (<Extent2>.[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS <C1> FROM (SELECT TOP (10) <c>.[Id] AS [Id] FROM [dbo].[Customers] AS [c] ) AS [Limit1] LEFT OUTER JOIN [dbo].[Addresses] AS [Extent2] ON [Limit1].[Id] = [Extent2].[Customer_Id] ) AS [Project1] ORDER BY [Project1].[Id] ASC, [Project1].[C1] ASC
Oskar - sorry but I mean can't use a fetch="subselect", I don't try subquery
Alexander Zaytsev added a comment — 2nd May 2013, 15:25:51:
NH-3456 has a test case
Lee Timmins added a comment — 2nd May 2013, 20:59:28:
Issue 3456 includes an attached test case which may help.
Dawid Ciecierski added a comment — 15th May 2014, 14:24:06:
Definitely agree that while the underlying mechanics explain themselves, for a regular user the behaviour may and should seem broken and should therefore be fixed.
The status is listed as 'Confirmed', but there seems to have been little activity over past months. Is anyone working on a solution? If not, would anyone care to outline a solution that would be acceptable by the core team so that we can start working on a patch? Should we automatically rework the query or perhaps automatically issue another one..?
EDIT: Sławomir, any chance you could add skip to the mix and show us what EF would generate for your particular sample case?
Lee Timmins added a comment — 16th May 2014, 11:20:35:
<~chrazz> I have just done a simple example to show you the query generated by Entity Framework. Imagine a blog with the following entities:
public class Post { public int PostId { get; set; } public string Name { get; set; } public virtual IList<Comment> Comments { get; private set; } public Post() { Comments = new List<Comment>(); } } public class Comment { public int CommentId { get; set; } public virtual Post Post { get; set; } }
If I said the following in entity framework:
_context.Posts .Include("Comments") .Take(3) .ToList();
It would generate the following query:
SELECT <Project1>.[PostId] AS [PostId], [Project1].[Name] AS [Name], [Project1].[C1] AS [C1], [Project1].[CommentId] AS [CommentId], [Project1].[Post*PostId] AS [Post*PostId] FROM ( SELECT <Limit1>.[PostId] AS [PostId], [Limit1].[Name] AS [Name], [Extent2].[CommentId] AS [CommentId], [Extent2].[Post*PostId] AS [Post*PostId], CASE WHEN (<Extent2>.[CommentId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS <C1> FROM (SELECT TOP (3) <c>.[PostId] AS [PostId], [c].[Name] AS [Name] FROM [dbo].[Posts] AS [c] ) AS [Limit1] LEFT OUTER JOIN [dbo].[Comments] AS [Extent2] ON [Limit1].[PostId] = [Extent2].[Post_PostId] ) AS [Project1] ORDER BY [Project1].[PostId] ASC, [Project1].[C1] ASC
Dawid Ciecierski added a comment — 16th May 2014, 11:32:50:
Thank you but this looks similar to the example posted above. I wanted to see if Skip changes the query much so that we could get a better picture in preparing a fix.
Ricardo Peres added a comment — 16th May 2014, 13:57:17:
Workaround:
session.Query
().Where(x => session.Query().Select(y => y.CustomerId).Take(10).Skip(5).Contains(x.Customer.CustomerId)).Select(x => x.Customer).ToList();
Lee Timmins added a comment — 16th May 2014, 13:59:44:
Oh right, Here's a couple examples. First the following:
_context.Posts .Include("Comments") .OrderBy(p => p.PostId) .Skip(2) .Take(3) .ToList();
Produces the following query:
SELECT <Project1>.[PostId] AS [PostId], [Project1].[Name] AS [Name], [Project1].[C1] AS [C1], [Project1].[CommentId] AS [CommentId], [Project1].[Post*PostId] AS [Post*PostId] FROM ( SELECT <Limit1>.[PostId] AS [PostId], [Limit1].[Name] AS [Name], [Extent2].[CommentId] AS [CommentId], [Extent2].[Post*PostId] AS [Post*PostId], CASE WHEN (<Extent2>.[CommentId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS <C1> FROM (SELECT TOP (3) <Extent1>.[PostId] AS [PostId], [Extent1].[Name] AS [Name] FROM ( SELECT <Extent1>.[PostId] AS [PostId], [Extent1].[Name] AS [Name], row*number() OVER (ORDER BY <Extent1>.[PostId] ASC) AS [row*number] FROM [dbo].[Posts] AS [Extent1] ) AS [Extent1] WHERE [Extent1].[row_number] > 2 ORDER BY [Extent1].[PostId] ASC ) AS [Limit1] LEFT OUTER JOIN [dbo].[Comments] AS [Extent2] ON [Limit1].[PostId] = [Extent2].[Post_PostId] ) AS [Project1] ORDER BY [Project1].[PostId] ASC, [Project1].[C1] ASC
And the following:
_context.Posts .Include("Comments") .OrderBy(p => p.PostId) .Take(3) .Skip(2) .ToList();
Produces the following query:
SELECT <Project1>.[PostId] AS [PostId], [Project1].[Name] AS [Name], [Project1].[C1] AS [C1], [Project1].[CommentId] AS [CommentId], [Project1].[Post*PostId] AS [Post*PostId] FROM ( SELECT <Skip1>.[PostId] AS [PostId], [Skip1].[Name] AS [Name], [Extent2].[CommentId] AS [CommentId], [Extent2].[Post*PostId] AS [Post*PostId], CASE WHEN (<Extent2>.[CommentId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS <C1> FROM (SELECT <Limit1>.[PostId] AS [PostId], [Limit1].[Name] AS [Name] FROM ( SELECT <Limit1>.[PostId] AS [PostId], [Limit1].[Name] AS [Name], row*number() OVER (ORDER BY <Limit1>.[PostId] ASC) AS [row*number] FROM ( SELECT TOP (3) <Extent1>.[PostId] AS [PostId], [Extent1].[Name] AS [Name] FROM [dbo].[Posts] AS [Extent1] ORDER BY [Extent1].[PostId] ASC ) AS [Limit1] ) AS [Limit1] WHERE [Limit1].[row_number] > 2 ) AS [Skip1] LEFT OUTER JOIN [dbo].[Comments] AS [Extent2] ON [Skip1].[PostId] = [Extent2].[Post_PostId] ) AS [Project1] ORDER BY [Project1].[PostId] ASC, [Project1].[C1] ASC
Please note that Entity Framework threw an error if an OrderBy expression did not exist before Skip.
Hope this helps.
Dawid Ciecierski added a comment — 16th May 2014, 14:27:03:
Thank you. Skip and Take with no OrderBy exception is the usual behaviour as NHibernate, EF or SQL (ROW_NUMBER) are not able to ascertain what exactly you might want to have your results numbered by.