如何在SQL中检索给定StoredProcedure参数的.NET类型?

我正在SQL程序之上创建’通用’包装器,我可以解析所有必需参数的名称和sqltypes,但有没有办法如何获得它的’底层’.NET类型?

我的目标是做一些事情:

SqlParameter param; object value; object correctParam = param.GetNETType().GetMethod("Parse", new Type[] { typeof(string) }).Invoke(value.ToString()); param.Value = correctParam; 

GetNETType是我需要的东西。 我知道它可以写成param.SqlDbType中的开关,但这是更短的方式,更短的注释代码意味着更低的维护:)

不幸的是,据我所知,这个映射没有在.NET Framework中的代码中公开。 我之前已经查看了.NET Framework参考源,发现在.NET代码中有很多很长的每类型切换语句,就像你想要避免的那样,但它们似乎都没有暴露在外面。

如果你真的只想从SqlTypes映射到最可能的.NET类型,我认为你最好的办法是简单地将MSDN文档中的映射表转换为代码。 请注意,MSDN上的表有(至少)两个错误:#1:没有名为“DateTime2”的.NET类型(我使用DateTime),也没有名为“Xml”的类型(我使用的是SqlXml)。

无论如何,这是我一直在使用的映射 – 使用Dictionary而不是开关,以便于访问而无需单独的方法。

 public static Dictionary TypeMap = new Dictionary { { SqlDbType.BigInt, typeof(Int64) }, { SqlDbType.Binary, typeof(Byte[]) }, { SqlDbType.Bit, typeof(Boolean) }, { SqlDbType.Char, typeof(String) }, { SqlDbType.Date, typeof(DateTime) }, { SqlDbType.DateTime, typeof(DateTime) }, { SqlDbType.DateTime2, typeof(DateTime) }, { SqlDbType.DateTimeOffset, typeof(DateTimeOffset) }, { SqlDbType.Decimal, typeof(Decimal) }, { SqlDbType.Float, typeof(Double) }, { SqlDbType.Int, typeof(Int32) }, { SqlDbType.Money, typeof(Decimal) }, { SqlDbType.NChar, typeof(String) }, { SqlDbType.NText, typeof(String) }, { SqlDbType.NVarChar, typeof(String) }, { SqlDbType.Real, typeof(Single) }, { SqlDbType.SmallInt, typeof(Int16) }, { SqlDbType.SmallMoney, typeof(Decimal) }, { SqlDbType.Structured, typeof(Object) }, // might not be best mapping... { SqlDbType.Text, typeof(String) }, { SqlDbType.Time, typeof(TimeSpan) }, { SqlDbType.Timestamp, typeof(Byte[]) }, { SqlDbType.TinyInt, typeof(Byte) }, { SqlDbType.Udt, typeof(Object) }, // might not be best mapping... { SqlDbType.UniqueIdentifier, typeof(Guid) }, { SqlDbType.VarBinary, typeof(Byte[]) }, { SqlDbType.VarChar, typeof(String) }, { SqlDbType.Variant, typeof(Object) }, { SqlDbType.Xml, typeof(SqlXml) }, }; 

请注意,您需要注意的一件事是大小/精度 – 某些SQL类型(例如varchar )具有大小限制,而.NET类型(例如string )则没有。 因此,能够了解最可能的.NET类型并不足够……如果您正在使用它,例如驱动器validation规则,您还需要能够阻止用户输入无效(例如,太大) )通过了解更多有关参数的值,如精度。 请注意,如果查看SqlClient源代码,它们会使用特殊代码来处理从相应的SQL精度设置Decimal类型的精度等情况。

请注意,如果您需要.NET类型的唯一原因是能够将数据填充到存储的proc参数中,您可能想要尝试在所有.NET值上使用ToString(),将字符串填充到Value属性中SqlParameter,看看框架是否会为你做转换/解析。 例如,对于XML或Date参数,您可以通过发送字符串来逃避。

此外,不是使用reflection在每种类型上查找Parse()方法,因为有一个已知(和小)的类型列表,您可以通过对每个类型使用强类型解析代码来获得更好的性能,如下面的代码。 (请注意,有几种类型(例如SqlDbType.Udt)不一定有明显的解析器方法 – 您需要弄清楚如何处理这些方法。)

 public static Dictionary> TypeMapper = new Dictionary> { { SqlDbType.BigInt, s => Int64.Parse(s)}, { SqlDbType.Binary, s => null }, // TODO: what parser? { SqlDbType.Bit, s => Boolean.Parse(s) }, { SqlDbType.Char, s => s }, { SqlDbType.Date, s => DateTime.Parse(s) }, { SqlDbType.DateTime, s => DateTime.Parse(s) }, { SqlDbType.DateTime2, s => DateTime.Parse(s) }, { SqlDbType.DateTimeOffset, s => DateTimeOffset.Parse(s) }, { SqlDbType.Decimal, s => Decimal.Parse(s) }, { SqlDbType.Float, s => Double.Parse(s) }, { SqlDbType.Int, s => Int32.Parse(s) }, { SqlDbType.Money, s => Decimal.Parse(s) }, { SqlDbType.NChar, s => s }, { SqlDbType.NText, s => s }, { SqlDbType.NVarChar, s => s }, { SqlDbType.Real, s => Single.Parse(s) }, { SqlDbType.SmallInt, s => Int16.Parse(s) }, { SqlDbType.SmallMoney, s => Decimal.Parse(s) }, { SqlDbType.Structured, s => null }, // TODO: what parser? { SqlDbType.Text, s => s }, { SqlDbType.Time, s => TimeSpan.Parse(s) }, { SqlDbType.Timestamp, s => null }, // TODO: what parser? { SqlDbType.TinyInt, s => Byte.Parse(s) }, { SqlDbType.Udt, s => null }, // consider exception instead { SqlDbType.UniqueIdentifier, s => new Guid(s) }, { SqlDbType.VarBinary, s => null }, // TODO: what parser? { SqlDbType.VarChar, s => s }, { SqlDbType.Variant, s => null }, // TODO: what parser? { SqlDbType.Xml, s => s }, }; 

上面使用的代码非常简单,例如:

  string valueToSet = "1234"; SqlParameter p = new SqlParameter(); p.SqlDbType = System.Data.SqlDbType.Int; p.Value = TypeMapper[p.SqlDbType](valueToSet); 

似乎没有人想告诉你,但你所做的可能不是最好的方法。

 object correctParam = param.GetNETType().GetMethod("Parse", new Type[] { typeof(string) }).Invoke(value.ToString()); param.Value = correctParam; 

你说你给了一个字符串值,你知道它必须分配给一个参数,你想要以任何方式填充那个值吗?

请考虑一下为什么这样做。 您假设以下代码是正确的:

 param.Value = NetType.Parse(value.toString()) 

没有明确的理由说明为什么这比以下更好:

 param.Value = value; 

但是既然你想要这样做,假设你已经尝试过这个并且发现你真正的问题是value不是参数的正确类型似乎是安全的。 因此,您需要一个可以运行的神奇修复程序,它始终确保该value是正确的类型。 您真正想要的可能是:

SetParam(param, value);

此函数将值填充到参数中。 如果value不是像你说的那样只是类型object ,这实际上会使事情变得容易一些,但是它有一个真实的类型(比如intstring )。 这是因为您可以使用方法重载,如SetParam(SqlParam param, int value)或generics来推断值类型SetParam(SqlParam param, T value)

所以我们知道你想要的function,我们不知道为什么。 在大多数合理的场景中,您可以了解值的类型,并且还了解参数的类型。 您正在寻找一种方法,将与参数不匹配的值塞入您不理解的参数中。

我可以为这个请求考虑两个主要原因:

  1. 实际上,您知道类型是兼容的,并且正在寻找一种通用的方法来避免编写大量代码。 所以你知道你正在尝试将一个long分配给一个SqlInt参数,并依赖于字符串转换来解决类型安全问题。

  2. 您并不真正了解您正在使用的代码,并且正在尝试修补修补程序以使某些工作正常。

真正重要的是要诚实地告诉自己你处于哪种情况。如果你是第一种情况,那么你可以写一个像我上面描述的SetParam这样的方法相当容易。 您将不得不编写一个switch语句(或者像上面的最佳答案,字典查找)。 你将不得不失去精确度(将一个long转换为int对大数字不起作用,但你的Parse也不行)但它会起作用。

如果你是第二种情况,请停一分钟。 认识到你将来会为自己设置更多错误(因为转换为字符串和从字符串转换不会解决你不了解类型的问题)。 您知道自己需要帮助,这就是为什么您使用Stack Overflow并提供帮助的原因,并且您正在处理您不理解的代码库。 我现在可以从你的问题中告诉你,你要挖掘自己比你意识到的更深的洞,如果这是你的情况,因为你已经拒绝了最好的答案(根据参数类型做一个转换语句)没有充分的理由。

因此,如果您处于第二种情况,那么最有助于您的事情不是Stack Overflow的答案,除非您愿意更完整地描述您的真实问题。 什么会帮助你理解价值来自哪里(它是UI?它是一个不同的子系统,它们遵循哪些规则?这些类型不匹配的原因吗?)以及它们的去向(什么是您正在调用的存储过程的定义?定义为什么参数类型?)。 我想你甚至可能不需要进入SQL来找到它,因为无论谁给你SqlParam可能已经为你正确定义了它。 如果你定义了它,你确实需要立即转到SQL来解决它。

我想你在这里错过了一步。 您需要做的第一件事是通过select调用和内部连接查询数据库以获取存储过程的定义,或者使用管理包装器。 然后,您可以根据返回的信息“推断”参数类型。

这是一个MSO lin k,可以帮助您入门

以及如何直接查询数据库结构的示例

如果您针对数据库运行第二个示例中的sql,您将看到确切的内容:

 USE AdventureWorks; GO SELECT SCHEMA_NAME(SCHEMA_ID) AS [Schema], SO.name AS [ObjectName], SO.Type_Desc AS [ObjectType (UDF/SP)], P.parameter_id AS [ParameterID], P.name AS [ParameterName], TYPE_NAME(P.user_type_id) AS [ParameterDataType], P.max_length AS [ParameterMaxBytes], P.is_output AS [IsOutPutParameter] FROM sys.objects AS SO INNER JOIN sys.parameters AS P ON SO.OBJECT_ID = P.OBJECT_ID WHERE SO.OBJECT_ID IN ( SELECT OBJECT_ID FROM sys.objects WHERE TYPE IN ('P','FN')) ORDER BY [Schema], SO.name, P.parameter_id GO 

您不一定能够隐式准确地提取正确的.NET CTS(“底层”)类型,因为它可能会根据参数中的值而改变 – SqlParameter的.DbType和.SqlDbType是可变的,并且可由程序员明确设置(或代码 -生成引擎)在输出参数的情况下,.DbType / .SqlDbType即使在正确一段时间后也可能是错误的,例如,如果突然返回的值突然与.NET术语中的预期不同。 这些值由数据存储驱动,.NET SqlParameter尽可能地使用其显式类型进行处理。 应该认为SqlParameter的数据值在.NET术语中是弱类型的(由parm.Value属性的System.Object返回值certificate)。

你最好的选择是

  1. 使用其他海报概述的映射方法之一 – 当然,它有自己的隐含假设,即SQL参数类型对于其中的数据始终是正确的。
  2. 可能会测试从输出参数返回的值,并假设连续值属于同一类型。 当然,这完全取决于数据库。
  3. 找到另一种策略而不是依赖于Microsoft Sql命名空间 – 您将来可能会更加快乐。

测试.NET CTS类型的值看起来类似于System.Type t = paramInstance.Value.GetType(); Null会导致exception。 除非你拿出一些花哨的reflection技术,否则你仍然需要使用开关或if / else来适当地投射它。

如果您可以解析为正确的SqlType,Reflection将为您提供.NET类型的显式强制转换。 返回值将是基础System.Type。 缓存结果应该弥补第一次查找时的性能。

看看他们在linq到sql t4中做了什么,它看起来效果很好。

您可以通过查看代码找到所需内容。