我正在使用遗留代码库,需要调用我不允许修改的存储过程。此存储过程返回一行或多行验证数据。
结果集示例(两列,代码和文本):
0 "success"
OR
3 "short error"
4 "detailed error"
在程序本身中,消息选择为:
Select 0 as code, 'success' as text
问题:
我正在使用Entity Framework将此存储过程的结果映射到自定义类:
public class ValidationResult
{
public int code { get; set; }
public string text { get; set; }
}
电话本身:
var result = context.Database.SqlQuery<ValidationResult>(@"old_sproc").ToList();
我已经编写了一些集成测试,并注意到当过程返回成功消息时,0会以short
形式出现。当它返回非零消息时,它会以int
的形式出现。我假设将code
设置为int,short适合。不幸的是,我的成功测试得到以下异常:
从具体化的“System.Int16”类型到“System.Int32”类型的指定强制转换无效。
当我将code
切换为短片以使我的成功测试通过时,我的失败测试失败并出现以下异常:
从物化“System.Int32”类型到“System.Int16”类型的指定强制转换无效。
ADO.NET 回答
一种解决方案是回退到ADO.NET的SqlDataReader
对象,因此我将其作为后备解决方案。我想知道我是否可以在EF方面做些什么来实现这个目的。
答案 0 :(得分:6)
(这是对my previous answer的跟进。它只与sql-server-2012及以后相关。)
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"
实际上是"EXECUTE old_sproc"
的缩写T-SQL。我提到这个是因为很容易认为SqlQuery
以某种方式特别处理存储过程的名称;但不,这实际上是一个常规的T-SQL语句。
在这个答案中,我们将只修改您当前的SQL。
WITH RESULT SETS
子句的隐式类型转换:因此,让我们继续您正在做的事情:EXECUTE
通过SqlQuery
存储过程。从SQL Server 2012开始,EXECUTE
statement支持一个名为 WITH RESULT SETS
的可选子句,允许您指定希望返回的结果集。如果实际结果集与该规范不匹配,SQL Server将尝试执行隐式类型转换。
在您的情况下,您可能会这样做:
var sql = "EXECUTE old_sproc WITH RESULT SETS ((code INT, text VARCHAR(MAX)))";
var result = context.Database.SqlQuery<ValidationResult(sql).ToList();
添加的子句声明您希望返回一个具有code INT
和text VARCHAR(MAX)
列的结果集。重要的是code INT
:如果存储过程恰好为SMALLINT
生成code
值,SQL Server将为您执行转换为INT
。
隐式转化可能会让您更进一步:例如,您可以将code
指定为VARCHAR(…)
甚至NUMERIC(…)
(并将您的C#属性更改为string
或{{ 1}},分别)。
如果你正在使用Entity Framework的decimal
方法,那么它不可能比那个更整洁。
为了快速参考,以下是链接到MSDN参考页面的一些引用:
“执行期间返回的实际结果集可能与使用WITH RESULT SETS子句以下列方式之一定义的结果不同:结果集数量,列数,列名称,可为空性,和数据类型。“
“如果数据类型不同,则会执行对定义数据类型的隐式转换。”
我不知道。
实体框架最近在“代码优先”的方向上发展(在撰写本文时它的版本是6),这种趋势可能会持续下去。
Julie Lerman撰写的“编程实体框架代码优先”一书Rowan Miller(由O'Reilly于2012年出版)有一个简短的章节“使用存储过程”,其中包含两个代码示例;两者都使用SqlQuery
来映射存储过程的结果集。
我想如果这两位EF专家没有展示另一种映射存储过程的方法,那么EF目前可能不提供SqlQuery
的替代方案。
(P.S。:不可否认,OP的主要问题不是存储过程本身;它正在使EF执行自动类型转换。即使这样,我也不知道除此处所示的另一种方式。)
答案 1 :(得分:5)
如果你不能改变存储过程本身,你可以创建一个以某种方式改变数据的包装器存储过程,并使用EF调用它。
当然不理想,但可能是一种选择。
答案 2 :(得分:2)
(注意:如果您正在使用SQL Server 2012或更高版本,请参阅my follow-up answer,其中显示了更简洁,更简洁的方式来执行此处所述的相同操作。)< / p>
这是一个留在EF土地上的解决方案,不需要任何数据库架构更改。
由于您可以将任何有效的SQL传递给SqlQuery
方法,因此没有什么可以阻止您传递一个多语句脚本:
DECLARE
是一张临时表; EXECUTE
存储过程,INSERT
将其结果存入临时表; SELECT
是该临时表的最终结果。最后一步是您可以应用任何进一步的后处理,例如类型转换。
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();
答案 3 :(得分:0)
在实体框架数据建模器页面(Model Browser
)中,将功能映射更改为适用于ValidationResult
类的特定int或创建新的功能映射结果类,它具有适当的int并将其用作生成的DTO类。
由于我无法访问实际的数据库,因此我忽略了这个过程。相反,我提供了创建新功能映射或修改现有映射的过程。试验和错误将帮助您克服错误的功能映射。
让EF生成正确信息的另一个技巧是暂时删除存储过程并让新的一个返回存根选择,例如:
select 1 AS Code , 'Text' as text
RETURN @@ROWCOUNT
这样做的原因是,有时EF无法确定存储过程最终返回的内容。如果是这种情况,暂时创建存根返回并从中生成EF可以为映射提供清晰的图像。然后在更新后将sproc返回到其原始代码有时可以解决问题。
答案 4 :(得分:-1)
忽略int / short。对于相同的数字,文本总是相同的吗?得到文本。有一个开关盒。是的它是一个黑客,但除非你能解决问题的根源(并且你说你不被允许),那么你应该采用最少花费时间来创建的黑客攻击,并且不会导致问题下一个人维护代码。如果这个存储过程是遗留的,将来不会有任何新的结果。这个解决方案和一个很好的评论解决了这个问题,让你回到其他地方创造价值。
答案 5 :(得分:-2)
将静态消息代码转换为int:
Select cast(0 as int) as code, 'success' as text
这确保返回的文字与另一个查询返回的int一致。将ValidationResult.code
声明为int
。
注意:我知道我错过了关于SP无法修改的问题的部分,但鉴于这使得答案非常复杂,我将此处留给其他可能遇到同样问题的人,但能够通过修改SP更容易地解决它。如果SP中的返回类型不一致并且修改是一个选项,这确实有效。
答案 6 :(得分:-3)
如果您找不到更好的解决方案,可以使用一种解决方法。让它成为一个int。它适用于所有错误代码。如果您收到异常,则表示结果是成功的,因此您可以为该特定异常添加try / catch。它并不漂亮,取决于它运行多少可能会影响性能。
另一个想法是,您是否尝试将code
的类型更改为object
?