views:

84

answers:

5

I work on speed optimization of my application, and what I have found is that LINQ (or EF) is creating some strange SQL for me that works slow.

Here is some code :

SomeList.AddRange(_databaseView
                .Select(l=> new SomeViewModel
                                {
                                    Date = l.Date,
                                    Details = l.Details,
                                    Level = l.LevelName,
                                    Id = l.ViewID,
                                    Message = l.Message,
                                    ProjectName = l.projectName,
                                    StatusId = l.StatusID,
                                    StatusName = l.StatusName
                                })
                .Skip(50)
                .Take(25));

And in theory it should've created SQL statement that takes 25 record but profiler showes following SQL for it :

    SELECT [Extent1].[Date]  AS [Date],
       [Extent1].[ID]            AS [ID],
       [Extent1].[LevelID]       AS [LevelID],
       [Extent1].[StatusID]      AS [StatusID],
       [Extent1].[projectName]   AS [projectName],
       [Extent1].[LevelName]     AS [LevelName],
       [Extent1].[StatusName]    AS [StatusName],
       [Extent1].[Message]       AS [Message],
       [Extent1].[Details]       AS [Details],
       [Extent1].[LogViewID]     AS [LogViewID]
FROM   (SELECT [v_MyView].[Date]       AS [Date],
               [v_MyView].[ProjectID]     AS [ProjectID],
               [v_MyView].[LevelID]       AS [LevelID],
               [v_MyView].[StatusID]      AS [StatusID],
               [v_MyView].[projectName]   AS [projectName],
               [v_MyView].[LevelName]     AS [LevelName],
               [v_MyView].[StatusName]    AS [StatusName],
               [v_MyView].[Message]       AS [Message],
               [v_MyView].[Details]       AS [Details],
               [v_MyView].[ViewID]        AS [ID]
        FROM   [dbo].[v_MyView] AS [v_MyView]) AS [Extent1]

_databaseView is IQueryable object on which all my sorting and filtering logic is done.

Here is something I figured : if I don't do any filtering SQL is normal with SELECT TOP (25) on it. But whenever I do filtering something gets messed up. Here is the code to one of my filters:

if (Filters.ProjectName != null && Filters.ProjectName[0] != 0)    // check if "all" is not checked
    _databaseView = Filters.ProjectName
        .Join(_databaseView,  f => f, l => l.ProjectID,  (f,l) => new SomeViewModel
                                                                           {
                                                                               Date = l.Date,
                                                                               Details = l.Details,
                                                                               LevelName = l.LevelName,
                                                                               ViewID = l.ViewID,
                                                                               Message = l.Message,
                                                                               projectName = l.projectName,
                                                                               StatusID = l.StatusID,
                                                                               StatusName = l.StatusName
                                                                           })
    .AsQueryable();

And it is without any constraint. How do I make this LINQ-EF thing to produce some good SQL ?

Thx in advance!

+2  A: 

The only way you can actually change the SQL that is being used would be to write your own and use that instead of using the generated SQL.

You mean the Skip and Take portions of the LINQ not being converted to SQL. I think it is because of the way that you are doing the LINQ.

Try something like

(From l In DataBaseView Select new SomeViewModel
                                {
                                    Date = l.Date,
                                    Details = l.Details,
                                    Level = l.LevelName,
                                    Id = l.ViewID,
                                    Message = l.Message,
                                    ProjectName = l.projectName,
                                    StatusId = l.StatusID,
                                    StatusName = l.StatusName
                                }).Skip(50).Take(25)

Instead and see if it makes a difference in the generated code.

Edit Somehow I missed the portion where you said it should be SQL that takes 25 records.

msarchet
A: 

If you can't get the SQL to perform well enough with the proper indexes then you could always try writing a stored procedure and just calling that from LINQ.

Dismissile
I have already written some stored procedure for this one and all the Indexes are set... so the only problem that is left is with correct skip-take logic.
Nazar Gargol
You can add paging to a stored procedure pretty easily. Use a Common Table Expression with the ROW_NUMBER() function.
Dismissile
Unnecessary, here, and hurts composability.
Craig Stuntz
I would prefer to keep paging where it is. This way I keep my architecture more clean.
Nazar Gargol
+2  A: 

LINQ Parser definitely consider the Skip and Take methods in your LINQ to Entities query and produces the correct expression tree and then Object Services converts the expression tree to a command tree which will be passed to the database provider for the specific SQL query generation.
In this case this 2 methods affecting the generated SQL with a WHERE [Extent1].[row_number] > 50 and SELECT TOP (25) for Skip and Take respectively.

Now, are you sure that you are looking at the right trace in the Profiler? I suggest take a look at ObjectQuery.ToTraceString method by writing the below code before going to Profiler and then debug through your code and examine the value of sql variable:

var query = _DatabaseView.Select(l=> new SomeViewModel {
                                                     Date = l.Date,
                                                     Details = l.Details,
                                                     Level = l.LevelName,
                                                     Id = l.ViewID,
                                                     Message = l.Message,
                                                     ProjectName = l.projectName,
                                                     StatusId = l.StatusID,
                                                     StatusName = l.StatusName})
                         .Skip(50)
                         .Take(25));
string sql = (query as ObjectQuery).ToTraceString();
Morteza Manavi
A: 

Try moving the Skip and Take before the Select.

Will
I did... you can't even imagine how many places I've put it =)
Nazar Gargol
Any luck taming EF in the end?
Will
+3  A: 

You don't say what _DatabaseView is, but my wild guess based on your results is that it's not an ObjectQuery<T>. Which would explain your problem. ObjectQuery will convert to SQL; IEnumerable<T>.Skip() won't. Calling AsQueryable() on an enumerable is not enough to make this happen.

For example, this:

var foo = MyObjectContext.SomeEntitySet.AsEnumerable().AsQueryable().Take(10);

...won't put the TOP in the SQL.

But this:

var bar = MyObjectContext.SomeEntitySet.Take(10);

... will.

Once again: You haven't said what _DatabaseView is. Try this operation directly on your ObjectContext and you will see that it works. The bug is in the code you use to assign _DatabaseView, which you haven't shown us.

Craig Stuntz
Yep, pretty sure that's the case here.
Morteza Manavi
nope.. _DatabaseView is an IQueryable object. And I even did AsQueryalble() in the end of each sorting, filtering.... so it is 100% IQueryable.
Nazar Gargol
Maybe I wasn't clear enough. `AsQueryable()` won't fix the problem. I'll clarify the answer for you.
Craig Stuntz
I've found some similar code in here: http://www.squaredroot.com/2008/04/08/updated-pagedlist-class/ and this code does everything on IEnumerable.
Nazar Gargol
@Nazar, no, that is not how Troy Goode's `PagedList` class works. Read the code more carefully. I'm quite familiar with this code and it does **not** "do everything on `IEnumerable`."
Craig Stuntz
k. I've found what the problem was. At time when I was doing Count() on my IQueryable object it was somehow effected and converted to IEnumerable, and when filtering was done Join() method mas with array of int[] which casts the result to IEnumerable. Thx a lot to Craig and everybody else for trying to figure this thing out =)
Nazar Gargol