LINQ или Entity Framework создает неограниченный оператор SQL - PullRequest
3 голосов
/ 25 октября 2010

Я работаю над оптимизацией скорости моего приложения, и я обнаружил, что LINQ (или EF) создает какой-то странный SQL для меня, который работает медленно.

Вот код:

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));

И теоретически он должен был создать оператор SQL, который принимает 25 записей, но профилировщик показывает следующий SQL для него:

    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 - это объект IQueryable, для которого все мои сортировки и фильтрациилогика готова.

Вот что я понял: если я не делаю никакой фильтрации, SQL нормален с SELECT TOP (25) на нем.Но всякий раз, когда я делаю фильтрацию, что-то портится.Вот код одного из моих фильтров:

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();

И это без каких-либо ограничений.Как мне сделать эту вещь LINQ-EF для создания хорошего SQL?

Спасибо заранее!

Ответы [ 5 ]

3 голосов
/ 25 октября 2010

Вы не говорите, что такое _DatabaseView, но мое дикое предположение, основанное на ваших результатах, состоит в том, что это не ObjectQuery<T>. Что бы объяснить вашу проблему. ObjectQuery преобразуется в SQL; IEnumerable<T>.Skip() не будет. Вызов AsQueryable() на перечислимом не достаточно, чтобы это произошло.

Например, это:

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

... не вставит TOP в SQL.

Но это:

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

... будет.

Еще раз: Вы не сказали, что такое _DatabaseView . Попробуйте эту операцию прямо на вашем ObjectContext, и вы увидите, что она работает. Ошибка в коде, который вы используете для назначения _DatabaseView, который вы нам не показали.

2 голосов
/ 25 октября 2010

LINQ Parser определенно учитывает методы Skip и Take в вашем запросе LINQ to Entities и создает правильное дерево выражений, а затем Object Services преобразует дерево выражений в дерево команд, которое будет передано к поставщику базы данных для генерации конкретного запроса SQL.
В этом случае это 2 метода, влияющие на сгенерированный SQL с WHERE [Extent1].[row_number] > 50 и SELECT TOP (25) для Skip и Take соответственно.

Теперь, вы уверены, что смотрите на правильный профиль в Profiler ? Я предлагаю взглянуть на метод ObjectQuery.ToTraceString , написав приведенный ниже код, прежде чем перейти к Profiler , а затем выполнить отладку через ваш код и проверить значение переменной sql :

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();
2 голосов
/ 25 октября 2010

Единственный способ реально изменить используемый SQL-код - написать собственный и использовать его вместо сгенерированного SQL.

Вы подразумеваете, что части LINQ Пропустить и взять не конвертируются в SQL. Я думаю, что это из-за того, как вы делаете LINQ.

Попробуйте что-то вроде

(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)

Вместо этого, посмотрите, имеет ли это значение в сгенерированном коде.

Редактировать Каким-то образом я пропустил ту часть, где вы сказали, что SQL должен быть 25 записей.

0 голосов
/ 25 октября 2010

Попробуйте переместить Skip и Take до выбора.

0 голосов
/ 25 октября 2010

Если вы не можете заставить SQL работать достаточно хорошо с соответствующими индексами, вы всегда можете попробовать написать хранимую процедуру и просто вызвать ее из LINQ.

...