代码之家  ›  专栏  ›  技术社区  ›  nothrow

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

  •  12
  • nothrow  · 技术社区  · 15 年前

    我正在SQL过程之上创建“通用”包装器,我可以解析所有必需参数的名称和SQLType,但是有没有办法获取它的“底层”.NET类型?”?

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

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

    6 回复  |  直到 15 年前
        1
  •  13
  •   Justin Grant    15 年前

    不幸的是,据我所知,这个映射没有在.NET框架内的代码中公开。我以前看过.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精度设置十进制类型的精度。

    此外,由于存在已知(且较小)的类型列表,因此不必使用反射在每个类型上查找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
  •  4
  •   EWizard    15 年前

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

    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 不是参数的正确类型。因此,您需要一个可以运行的神奇修复程序,它将始终确保

    SetParam(param, value);

    其中,此函数将值填充到参数中。这实际上让事情变得更容易,如果 价值 不是简单的类型 object int string SetParam(SqlParam param, int value) 或泛型来推断值类型 SetParam<T>(SqlParam param, T value) .

    我们知道你想要的函数,但不知道为什么。在最合理的场景中,您知道值的类型,也知道参数的类型。您正在寻求一种方法,将与参数不匹配的值填充到您不理解的参数中。

    对于这个请求,我可以想到两个主要原因:

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

    对于你所处的情况,对自己诚实是非常重要的。如果是第一种情况,那么可以编写如下方法 SetParam

    如果你是第二种情况,停一下。认识到您正在为将来更多的bug做准备(因为转换字符串和从字符串转换不会解决您不理解类型的问题)。您知道您需要帮助,这就是为什么您处于堆栈溢出状态并提供悬赏以寻求帮助,而您正在处理一个您不理解的代码库。我现在可以从你的问题中看出,如果这是你的情况,你会给自己挖一个比你意识到的更深的洞,因为你已经没有充分的理由拒绝了最好的答案(根据参数类型执行switch语句)。

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

        3
  •  3
  •   Paul Sasik    15 年前

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

    这是一个 MSO lin 我想让你开始

    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
    
        4
  •  3
  •   John K    15 年前

    你不一定 含蓄准确

    你最好的选择是

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

    测试.NET CTS类型的值类似于 System.Type t = paramInstance.Value.GetType();

        5
  •  1
  •   Mark Brackett    15 年前

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

        6
  •  1
  •   Fredou    15 年前

    linq to sql t4 ,它似乎工作得很好。