WebApi oData генерирует несовместимый SQL

Я получаю сообщение об ошибке:

Параметры в качестве аргументов для подпунктов TOP и LIMIT в запросе или LimitExpression в дереве команд не поддерживаются в версиях SQL Server, предшествующих SQL Server 2005.

При использовании WebAPI + oData при выполнении Take() в LinqPad или через http:

http://localhost:8080/odata/sample()?$top=10

Однако, если я запускаю Take() непосредственно для DbContext, он работает нормально. Поэтому я предполагаю, что магия oData создает какой-то linq/sql, который не поддерживается в моей настройке.

Сложности, которые у меня есть, заключаются в том, что EDMX необходимо настроить для работы в режиме совместимости 80 (sql 2000).

И что я использую Composite Key в ASP.NET Web API OData (но теперь я понимаю, что это рекомендуемый подход из другого сообщения так что скорее всего проблема не в нем)

И это исходит из вида, а не из-за стола.

И я использую WebApi 1 (не 2).

Есть ли способ переопределить генерируемый SQL, чтобы я мог избежать несовместимого SQL?

ИЗМЕНИТЬ:

Пройдясь по webapi (а затем и по коду фреймворка сущностей) — поучительный опыт, я нашел строку, выбрасывающую исключение:

В \entityframework\src\EntityFramework.SqlServer\SqlGen\Sql8ConformanceChecker.cs

/// <summary>
/// Walks the structure
/// </summary>
/// <exception cref="NotSupportedException">expression.Limit is DbParameterReferenceExpression</exception>
public override bool Visit(DbLimitExpression expression)
{
    Check.NotNull(expression, "expression");

    if (expression.Limit is DbParameterReferenceExpression)
    {
        throw new NotSupportedException(Strings.SqlGen_ParameterForLimitNotSupportedOnSql8);
    }

    return VisitExpression(expression.Argument);
}

Но я все еще не уверен, почему я могу легко сделать OrderBy().Take() в контексте, но версия oData не работает.

Мне удалось сравнить DbQueryCommandTree, сгенерированный oData, с эквивалентом, сгенерированным при запросе dbContext.

Версия oData:

{DbQueryCommandTree
|_Parameters
| |_p__linq__0 : Edm.Int32
|_Query : Collection{Record['UserID'=Edm.Int32, 'RolledupReviewPeriodID'=Edm.Int32, 'UserEmployeeNumber'=Edm.String, 'FirstName'=Edm.String, 'LastName'=Edm.String, 'JobTitle'=Edm.String, 'CostCentre'=Edm.String, 'CostCentreNo'=Edm.String, 'Department'=Edm.String, 'Division'=Edm.String, 'Directorate'=Edm.String, 'AppraiserName'=Edm.String, 'ReviewDate'=Edm.DateTime, 'Status'=Edm.String, 'InterimRating'=Edm.Int32, 'IndicativeRating'=Edm.Int32, 'PreModerated'=Edm.Int32, 'ModeratedRating'=Edm.Int32, 'FinalRating'=Edm.Int32, 'ReviewPeriodName'=Edm.String]}
  |_Project
    |_Input : 'Limit1'
    | |_Limit
    |   |_Sort
    |   | |_Input : 'Extent1'
    |   | | |_Scan : PMM_ModelStoreContainer.vwReports_ReviewRatings
    |   | |_SortOrder
    |   |   |_Asc
    |   |   | |_Var(Extent1).RolledupReviewPeriodID
    |   |   |_Asc
    |   |     |_Var(Extent1).UserID
    |   |_@p__linq__0
    |_Projection
      |_NewInstance : Record['UserID'=Edm.Int32, 'RolledupReviewPeriodID'=Edm.Int32, 'UserEmployeeNumber'=Edm.String, 'FirstName'=Edm.String, 'LastName'=Edm.String, 'JobTitle'=Edm.String, 'CostCentre'=Edm.String, 'CostCentreNo'=Edm.String, 'Department'=Edm.String, 'Division'=Edm.String, 'Directorate'=Edm.String, 'AppraiserName'=Edm.String, 'ReviewDate'=Edm.DateTime, 'Status'=Edm.String, 'InterimRating'=Edm.Int32, 'IndicativeRating'=Edm.Int32, 'PreModerated'=Edm.Int32, 'ModeratedRating'=Edm.Int32, 'FinalRating'=Edm.Int32, 'ReviewPeriodName'=Edm.String]
        |_Column : 'UserID'
        | |_Var(Limit1).UserID
        |_Column : 'RolledupReviewPeriodID'
        | |_Var(Limit1).RolledupReviewPeriodID
        |_Column : 'UserEmployeeNumber'
        | |_Var(Limit1).UserEmployeeNumber
        |_Column : 'FirstName'
        | |_Var(Limit1).FirstName
        |_Column : 'LastName'
        | |_Var(Limit1).LastName
        |_Column : 'JobTitle'
        | |_Var(Limit1).JobTitle
        |_Column : 'CostCentre'
        | |_Var(Limit1).CostCentre
        |_Column : 'CostCentreNo'
        | |_Var(Limit1).CostCentreNo
        |_Column : 'Department'
        | |_Var(Limit1).Department
        |_Column : 'Division'
        | |_Var(Limit1).Division
        |_Column : 'Directorate'
        | |_Var(Limit1).Directorate
        |_Column : 'AppraiserName'
        | |_Var(Limit1).AppraiserName
        |_Column : 'ReviewDate'
        | |_Var(Limit1).ReviewDate
        |_Column : 'Status'
        | |_Var(Limit1).Status
        |_Column : 'InterimRating'
        | |_Var(Limit1).InterimRating
        |_Column : 'IndicativeRating'
        | |_Var(Limit1).IndicativeRating
        |_Column : 'PreModerated'
        | |_Var(Limit1).PreModerated
        |_Column : 'ModeratedRating'
        | |_Var(Limit1).ModeratedRating
        |_Column : 'FinalRating'
        | |_Var(Limit1).FinalRating
        |_Column : 'ReviewPeriodName'
          |_Var(Limit1).ReviewPeriodName}

Версия при прямом обращении к dbContext

{DbQueryCommandTree
|_Parameters
|_Query : Collection{Record['UserID'=Edm.Int32, 'RolledupReviewPeriodID'=Edm.Int32, 'UserEmployeeNumber'=Edm.String, 'FirstName'=Edm.String, 'LastName'=Edm.String, 'JobTitle'=Edm.String, 'CostCentre'=Edm.String, 'CostCentreNo'=Edm.String, 'Department'=Edm.String, 'Division'=Edm.String, 'Directorate'=Edm.String, 'AppraiserName'=Edm.String, 'ReviewDate'=Edm.DateTime, 'Status'=Edm.String, 'InterimRating'=Edm.Int32, 'IndicativeRating'=Edm.Int32, 'PreModerated'=Edm.Int32, 'ModeratedRating'=Edm.Int32, 'FinalRating'=Edm.Int32, 'ReviewPeriodName'=Edm.String]}
  |_Project
    |_Input : 'Limit1'
    | |_Limit
    |   |_Sort
    |   | |_Input : 'Extent1'
    |   | | |_Scan : PMM_ModelStoreContainer.vwReports_ReviewRatings
    |   | |_SortOrder
    |   |   |_Asc
    |   |   | |_Var(Extent1).RolledupReviewPeriodID
    |   |   |_Asc
    |   |     |_Var(Extent1).UserID
    |   |_10
    |_Projection
      |_NewInstance : Record['UserID'=Edm.Int32, 'RolledupReviewPeriodID'=Edm.Int32, 'UserEmployeeNumber'=Edm.String, 'FirstName'=Edm.String, 'LastName'=Edm.String, 'JobTitle'=Edm.String, 'CostCentre'=Edm.String, 'CostCentreNo'=Edm.String, 'Department'=Edm.String, 'Division'=Edm.String, 'Directorate'=Edm.String, 'AppraiserName'=Edm.String, 'ReviewDate'=Edm.DateTime, 'Status'=Edm.String, 'InterimRating'=Edm.Int32, 'IndicativeRating'=Edm.Int32, 'PreModerated'=Edm.Int32, 'ModeratedRating'=Edm.Int32, 'FinalRating'=Edm.Int32, 'ReviewPeriodName'=Edm.String]
        |_Column : 'UserID'
        | |_Var(Limit1).UserID
        |_Column : 'RolledupReviewPeriodID'
        | |_Var(Limit1).RolledupReviewPeriodID
        |_Column : 'UserEmployeeNumber'
        | |_Var(Limit1).UserEmployeeNumber
        |_Column : 'FirstName'
        | |_Var(Limit1).FirstName
        |_Column : 'LastName'
        | |_Var(Limit1).LastName
        |_Column : 'JobTitle'
        | |_Var(Limit1).JobTitle
        |_Column : 'CostCentre'
        | |_Var(Limit1).CostCentre
        |_Column : 'CostCentreNo'
        | |_Var(Limit1).CostCentreNo
        |_Column : 'Department'
        | |_Var(Limit1).Department
        |_Column : 'Division'
        | |_Var(Limit1).Division
        |_Column : 'Directorate'
        | |_Var(Limit1).Directorate
        |_Column : 'AppraiserName'
        | |_Var(Limit1).AppraiserName
        |_Column : 'ReviewDate'
        | |_Var(Limit1).ReviewDate
        |_Column : 'Status'
        | |_Var(Limit1).Status
        |_Column : 'InterimRating'
        | |_Var(Limit1).InterimRating
        |_Column : 'IndicativeRating'
        | |_Var(Limit1).IndicativeRating
        |_Column : 'PreModerated'
        | |_Var(Limit1).PreModerated
        |_Column : 'ModeratedRating'
        | |_Var(Limit1).ModeratedRating
        |_Column : 'FinalRating'
        | |_Var(Limit1).FinalRating
        |_Column : 'ReviewPeriodName'
          |_Var(Limit1).ReviewPeriodName}

Большое отличие заключается в версии oData, которую он передает в параметрах:

|_Parameters | |_p__linq__0 : Edm.Int32

В то время как версия dbcontext параметры просто передаются напрямую, т.е. "10"


person Alex KeySmith    schedule 08.04.2014    source источник


Ответы (2)


Покопавшись как крот в яме (слегка сошел с ума).

Я наткнулся на эту жемчужину:

 public static IQueryable Take(IQueryable query, int count, Type type, bool parameterize)
 {
     MethodInfo takeMethod = ExpressionHelperMethods.QueryableTakeGeneric.MakeGenericMethod(type);
     Expression takeValueExpression = parameterize ? LinqParameterContainer.Parameterize(typeof(int), count) : Expression.Constant(count);

     Expression takeQuery = Expression.Call(null, takeMethod, new[] { query.Expression, takeValueExpression });
     return query.Provider.CreateQuery(takeQuery);
 }

Внутри ExpressionHelpers.cs

Я хотел знать, на что повлияло изменение параметра:

Expression takeValueExpression = parameterize ? LinqParameterContainer.Parameterize(typeof(int), count) : Expression.Constant(count);

Просматривая стек, я увидел, что он был передан путем смешивания запроса и контекста для создания ODataQuerySettings.EnableConstantParameterization

Какое чтение Вызов Параметры запроса напрямую Я могу переопределить в контроллере (отлично!)

Таким образом, исправление в контроллере просто следующее:

 public IQueryable<T> Get(ODataQueryOptions<T> options)
 {
     ODataQuerySettings settings = new ODataQuerySettings()
     {
         EnableConstantParameterization = false
     };

     IQueryable results = options.ApplyTo(db.T.AsQueryable(), settings);

     return results as IQueryable<T>;
 }
person Alex KeySmith    schedule 09.04.2014

Алекс, здорово, что ты уже нашел решение этой проблемы. Мы добавили этот параметр конфигурации, EnableConstantParameterization`, для повышения производительности кэша запросов EF и SQL. Мы включаем его по умолчанию, так как мы видели действительно хорошие улучшения производительности при его включении. К сожалению, как вы уже заметили, он может не работать со старыми версиями SQL-сервера.

Вы можете отключить этот параметр с помощью ODataQuerySettings.EnableConstantParameterization, если вы используете ODataQueryOptions. Если вы используете QueryableAttribute, вы можете отключить это, установив [QueryableAttribute(EnableConstantParameterization = false)].

person RaghuRam Nadiminti    schedule 09.04.2014
comment
Спасибо RaghuRam, когда я наткнулся на EnableConstantParameterization, я подумал, что где-то может скрываться ActionFilterAttribute, но я просто не смог его найти :-) Я предполагаю, что он называется [Queryable] в WebAPI2, но [QueryableAttribute] в WebAPI 1? - person Alex KeySmith; 10.04.2014
comment
Кстати. Это отличная функция, я, вероятно, крайний случай, когда хочу использовать новую технологию (webapi) с SQL 2000 :-) - person Alex KeySmith; 10.04.2014
comment
Теперь я вижу, что добавление пространства имен [Queryable] работает (и [QueryableAttribute]) очень хорошо. - person Alex KeySmith; 10.04.2014