【问题标题】:WebApi oData generates incompatible SQLWebApi oData 生成不兼容的 SQL
【发布时间】:2014-04-08 16:30:18
【问题描述】:

我收到错误消息:

作为 TOP 子句和 LIMIT 子句的参数的参数 不支持查询或命令树中的 LimitExpression 早于 SQL Server 2005 的 SQL Server 版本。

在 LinqPad 中或通过 http 执行 Take() 时使用 WebAPI + oData:

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

但是,如果我直接对 DbContext 运行 Take(),它可以正常工作。所以我认为 oData 魔法正在创建一些我的设置不支持的 linq / sql。

我遇到的复杂情况是 EDMX 需要设置为在兼容模式 80 (sql 2000) 下运行。

我正在使用Composite Key in ASP.NET Web API OData(但我现在明白那是the recommended approach from another post,所以可能不是问题)。

而且它来自视图,而不是表格。

我使用的是 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 版本失败。

我设法比较了 oData 生成的 DbQueryCommandTree 与查询 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”

【问题讨论】:

    标签: sql entity-framework asp.net-web-api entity-framework-6 asp.net-web-api-odata


    【解决方案1】:

    在挖了一个像鼹鼠一样深的洞之后(有点发疯了)。

    我遇到了这个宝石:

     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 传入的

    我可以在控制器中覆盖 Invoking Query Options Directly 的哪个读数(太棒了!)

    因此,控制器中的修复方法如下:

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

    【讨论】:

      【解决方案2】:

      Alex,很高兴您已经找到了解决此问题的方法。我们添加了此配置设置 EnableConstantParameterization`,以提高 EF 和 SQL 查询缓存性能。我们默认打开它,因为我们已经看到启用此功能后性能得到了非常好的改进。不幸的是,正如您已经注意到的,它可能不适用于旧版本的 SQL Server。

      如果您使用的是ODataQueryOptions,则可以使用ODataQuerySettings.EnableConstantParameterization 禁用此设置。如果你使用QueryableAttribute,你可以通过设置[QueryableAttribute(EnableConstantParameterization = false)]来禁用它。

      【讨论】:

      • 感谢 RaghuRam,当我遇到“EnableConstantParameterization”时,我认为可能有一个 ActionFilterAttribute 隐藏在某处我只是找不到它:-) 我猜它在 WebAPI2 中称为 [Queryable] 但 [QueryableAttribute ] 在 WebAPI 1 中?
      • 顺便说一句。这是一个很棒的功能,我可能是一个希望在 SQL 2000 中使用新技术(webapi)的边缘案例 :-)
      • 我现在可以看到引入命名空间 [Queryable] 工作(和 [QueryableAttribute])非常好。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-29
      • 2016-02-10
      • 1970-01-01
      相关资源
      最近更新 更多