【问题标题】:How to retrieve .NET type of given StoredProcedure's Parameter in SQL?如何在 SQL 中检索给定 StoredProcedure 参数的 .NET 类型?
【发布时间】:2009-10-20 13:42:56
【问题描述】:

我在 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 内部的 switch,但这是更短的方式,更短的注释代码意味着更低的维护:)

【问题讨论】:

  • 你到底想做什么?当我读到您有一个 SqlParameters 集合并且您有一个 .net 值集合时,我是否正确?并且您想将您的值集合“转换”为 SqlParameter.Value 属性?示例:SqlParamaters[0].Value = MagicConvert(Values[0]);

标签: c# .net sql reflection


【解决方案1】:

不幸的是,据我所知,此映射并未在 .NET Framework 内的代码中公开。为此,我之前查看了 .NET Framework 参考源,发现在 .NET 代码中,有很多很长的每种类型的 switch 语句,就像您试图避免的那样,但似乎没有一个要暴露在外面。

如果您真的只是想从 SqlTypes 映射到最喜欢的 .NET 类型,我认为您最好的选择是将映射表 in the MSDN docs 转换为代码。请注意,MSDN 上的表格(至少)有两个错误:#1:没有名为“DateTime2”的 .NET 类型(我使用了 DateTime),也没有名为“Xml”的类型(我使用了 SqlXml)。

无论如何,这是我一直在使用的映射 - 使用字典而不是开关,以便在没有单独方法的情况下轻松访问。

public static Dictionary<SqlDbType, Type> TypeMap = new Dictionary<SqlDbType, Type>
{
    { 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 类型是不够的......如果你使用它来驱动验证规则,你还需要能够防止用户输入无效(例如太大) 通过了解更多关于参数(如精度)的值。请注意,如果您查看 SqlClient 源代码,它们会使用特殊代码来处理情况,例如根据相应的 SQL 精度设置 Decimal 类型的精度。

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

此外,由于有一个已知(且很小)的类型列表,您可以通过为每个类型使用强类型解析代码来获得更好的性能,而不是使用反射来查找每种类型的 Parse() 方法,例如代码以下。 (请注意,几种类型(例如 SqlDbType.Udt)不一定具有明显的解析器方法——您需要弄清楚您想要如何处理这些。)

public static Dictionary<SqlDbType, Func<string, object>>  TypeMapper = new Dictionary<SqlDbType, Func<string, object>>
{
    { 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);

【讨论】:

    【解决方案2】:

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

    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) 之类的方法重载或泛型来推断值类型 SetParam&lt;T&gt;(SqlParam param, T value)

    所以我们知道你想要的功能,但我们不知道为什么。在大多数合理的场景中,您对值的类型有所了解,并且您也对参数的类型有所了解。您正在寻求一种将与参数不匹配的值填充到您不理解的参数中的方法。

    我能想到这个请求有两个主要原因:

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

    2. 您并不真正了解您正在使用的代码,并试图修补补丁以使某些东西正常工作。

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

    如果您是第二种情况,请稍等片刻。认识到您正在为将来的更多错误做好准备(因为转换为字符串并从字符串转换不会解决您不了解类型的问题)。您知道您需要帮助,这就是您在 Stack Overflow 上并提供赏金寻求帮助的原因,并且您正在处理您不理解的代码库。我现在可以从您的问题中看出,如果这是您的情况,您将为自己挖一个比您意识到的更深的洞,因为您已经无缘无故拒绝了最佳答案(根据参数类型执行 switch 语句) .

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

    【讨论】:

      【解决方案3】:

      我认为你在这里错过了一步。您需要做的第一件事是通过选择调用和内部连接到 sys 对象表或使用管理包装器来查询数据库以获取存储过程的定义。然后就可以根据返回的信息“推断”出参数的类型了。

      这里有一个MSO link 帮助您开始

      还有一个how to query the database structure直接的例子

      如果您针对您的数据库运行第二个示例中的 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
      

      【讨论】:

      • 我不确定它是如何完成的(我正在扩展其他人的代码),但通过 SqlCommand.Parameters 进行交互向我展示了所有必需的参数。如何推断类型?
      • 你无法真正推断出类型。你必须以某种方式知道类型。通常类型是硬编码的,但您正在寻找一种自动化的方式,而“了解”类型的唯一方法是查询数据库。
      • 我认为他可能只需要一种从 SQL 类型映射到相应 .NET 类型的方法。例如Varchar => 字符串等
      • Knobloch 是对的。我想知道,除了 switch(param.SqlDbType) 之外是否还有其他映射
      【解决方案4】:

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

      你最好的选择是

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

      测试 .NET CTS 类型的值类似于System.Type t = paramInstance.Value.GetType(); Null 将导致异常。您仍然需要使用 switch 或 if/else 适当地转换它,除非您使用一些花哨的反射技术。

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          看看他们在linq to sql t4做了什么,看起来效果不错。

          你也许可以通过查看代码找出你需要的东西。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-02-14
            • 2018-12-23
            • 1970-01-01
            • 2012-09-13
            • 1970-01-01
            • 1970-01-01
            • 2017-04-25
            • 2014-04-18
            相关资源
            最近更新 更多