存储过程有时 returns short,有时 returns int

Stored Procedure sometimes returns short, sometimes returns int

我正在使用遗留代码库,需要调用一个存储过程,我不允许修改。此存储过程returns 一行或多行验证数据。

结果集示例(两列,代码和文本):

0 "success"

3 "short error"
4 "detailed error"

在程序本身中,消息被简单地选择为:

Select 0 as code, 'success' as text

问题:

我正在使用 Entity Framework 将此存储过程的结果映射到自定义 class:

public class ValidationResult
{
    public int code { get; set; }
    public string text { get; set; }
}

调用本身:

var result = context.Database.SqlQuery<ValidationResult>(@"old_sproc").ToList();

我写了一些集成测试,并注意到当过程 returns 成功消息时,0 以 short 的形式出现。当它 returns 是一个非零消息时,它会显示为 int。我假设将 code 设置为 int,short 会适合。不幸的是,我的成功测试出现以下异常:

The specified cast from a materialized 'System.Int16' type to the 'System.Int32' type is not valid.

当我将 code 切换为短路以使我的成功测试通过时,我的失败测试失败并出现以下异常:

The specified cast from a materialized 'System.Int32' type to the 'System.Int16' type is not valid.

ADO.NET是一个答案

一个解决方案是回退到 ADO.NET 的 SqlDataReader 对象,所以我将其作为回退解决方案。不过,我想知道我是否可以在 EF 方面做些什么来使它正常工作。

将静态消息代码转换为 int:

Select cast(0 as int) as code, 'success' as text

这确保文字 returned 与其他查询的 int returned 一致。将 ValidationResult.code 声明为 int

注意:我知道我错过了关于无法修改 SP 的问题的部分,但考虑到这会使答案变得相当复杂,我将此留给可能的其他人有同样的问题,但是可以通过修改 SP 更容易地解决它。 如果你在 SP 中有 return 类型不一致并且修改是一个选项,这确实有效。

如果您不能更改存储过程本身,您可以创建一个包装存储过程以某种方式更改数据,并让 EF 调用它。

当然不理想,但可能是一种选择。

如果您找不到更好的解决方案,可以使用一种解决方法。让它成为一个整数。它适用于所有错误代码。如果你得到一个异常,你知道结果是成功的,所以你可以为那个特定的异常添加一个 try/catch 。它并不漂亮,并且取决于它运行的次数,它可能会影响性能。

另一种想法,您是否尝试过将 code 的类型更改为 object

(注意: 如果您使用的是 SQL Server 2012 或更高版本,请参阅 ,它显示了一种更短、更简洁的方法做这里描述的同样的事情。)

这是一个保留在 EF 领域并且不需要任何数据库架构更改的解决方案。

由于您可以将任何有效的 SQL 传递给 SqlQuery 方法,因此没有什么能阻止您向它传递一个多语句脚本:

  1. DECLARE临时 table;
  2. EXECUTEs 存储过程和 INSERTs 其结果进入临时 table;
  3. SELECTs 临时 table 的最终结果。

最后一步是您可以应用任何进一步的 post 处理,例如类型转换。

const string sql = @"DECLARE @temp TABLE ([code] INT, [text] VARCHAR(MAX));
                     INSERT INTO @temp EXECUTE [old_sproc];
                     SELECT CONVERT(INT, [code]) AS [code], [text] FROM @temp;";
                     //     ^^^^^^^^^^^^^      ^^^^^^^^^^^
                     // this conversion might not actually be necessary
                     // since @temp.code is already declared INT, i.e.
                     // SQL Server might already have coerced SMALLINT
                     // values to INT values during the INSERT.

var result = context.Database.SqlQuery<ValidationResult>(sql).ToList();

(这是 . It is only relevant for 及以后的跟进。)

简答:

var sql = "EXECUTE old_sproc WITH RESULT SETS ((code INT, text VARCHAR(MAX)))";
var result = context.Database.SqlQuery<ValidationResult(sql).ToList();

本回答采用的方法:

此答案将跟随您的脚步并使用 SqlQuery 来执行您的存储过程。 (为什么不采用完全不同的方法?因为可能没有其他选择。我将在下面进一步讨论。)

让我们从对您当前代码的观察开始:

var result = context.Database.SqlQuery<ValidationResult>(@"old_sproc").ToList();

查询文字"old_sproc"确实缩写为T-SQL for "EXECUTE old_sproc"。我提到这一点是因为很容易认为 SqlQuery 以某种方式特殊对待存储过程的名称;但是不,这实际上是一个常规的 T-SQL 语句。

在这个回答中,我们将稍微修改您当前的SQL。

使用 WITH RESULT SETS 子句的隐式类型转换:

所以让我们继续您已经在做的事情:EXECUTE 通过 SqlQuery 的存储过程。从 SQL Server 2012 开始,EXECUTE statement 支持一个名为 WITH RESULT SETS 的可选子句,它允许您指定希望返回的结果集。 SQL 如果实际结果集不符合该规范,服务器将尝试执行隐式类型转换。

在你的情况下,你可以这样做:

var sql = "EXECUTE old_sproc WITH RESULT SETS ((code INT, text VARCHAR(MAX)))";
var result = context.Database.SqlQuery<ValidationResult(sql).ToList();

添加的子句声明您希望返回一个包含 code INTtext VARCHAR(MAX) 列的结果集。重要的一点是 code INT:如果存储过程恰好为 code 生成 SMALLINT 值,SQL 服务器将为您执行到 INT 的转换。

隐式转换可能会让您走得更远:例如,您可以将 code 指定为 VARCHAR(…) 甚至 NUMERIC(…)(并将 C# 属性更改为 stringdecimal,分别)。

如果您使用的是 Entity Framework 的 SqlQuery 方法,则不太可能比这更整洁。

为了快速参考,以下是链接到 MSDN 参考页的一些引述:

"The actual result set being returned during execution can differ from the result defined using the WITH RESULT SETS clause in one of the following ways: number of result sets, number of columns, column name, nullability, and data type."

"If the data types differ, an implicit conversion to the defined data type is performed."

我是否必须编写 SQL 查询?难道没有另一种(更多 ORM)方式吗?

None 我知道的。

Entity Framework 最近一直在朝着“代码优先”的方向发展(在撰写本文时是第 6 版),并且这种趋势可能会继续。

Julie Lerman 和 Rowan Miller 合着的“Programming Entity Framework Code First”一书(O'Reilly 于 2012 年出版)有一个简短的章节“Working with Stored Procedures”,其中包含两个代码示例;两者都使用 SqlQuery 映射存储过程的结果集。

我想如果这两位 EF 专家没有展示另一种映射存储过程的方法,那么 EF 目前可能没有提供 SqlQuery 的任何替代方法。

(P.S.: 不可否认,OP 的主要问题不是存储过程本身;它使 EF 执行自动类型转换。即便如此,除了这里显示的方法,我不知道还有另一种方法。 )

忽略int/short。对于相同的数字,文本总是相同的,对吗?只得到文本。有一个开关盒。是的,它是一个 hack,但除非你能解决问题的根源(并且你说你不被允许),否则你应该选择花费最少的时间来创建并且不会导致问题的 hack下一个维护代码的人。如果这个存储过程是遗留的,它将来不会有任何新的结果。这个解决方案加上一个很好的评论解决了这个问题,让你回到其他地方创造价值。

在 entity framework 数据建模器页面 (Model Browser) 中,将 functional 映射更改为适用于 ValidationResult class 或创建一个新的 函数映射 结果 class 具有适当的 int 并将其用作结果 DTO class.

我对这个过程有点模糊,因为我无权访问实际的数据库;相反,我提供了创建新功能映射或修改现有功能映射的过程。反复试验将帮助您克服不正确的功能映射。


让 EF 生成正确信息的另一个技巧是暂时删除存储过程并创建一个新的 return stub select,例如:

select  1 AS Code , 'Text' as text

RETURN @@ROWCOUNT

这样做的原因是,有时 EF 无法确定存储过程最终 return 是什么。如果是这种情况,临时创建 stub return 并从中生成 EF 可为映射提供清晰的画面。然后 return 在更新后将存储过程恢复到其原始代码有时会达到目的。