简体   繁体   English

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

[英]How to retrieve .NET type of given StoredProcedure's Parameter in SQL?

I'm creating 'generic' wrapper above SQL procedures, and I can resolve all required parameters' names and sqltypes, but is there any way how to get it's 'underlying' .NET type? 我正在SQL程序之上创建'通用'包装器,我可以解析所有必需参数的名称和sqltypes,但有没有办法如何获得它的'底层'.NET类型?

My goal is to do something like: 我的目标是做一些事情:

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

Where GetNETType is the thing I need. GetNETType是我需要的东西。 I know that it can be written as switch inside of param.SqlDbType, but this is shorter way, and shorter commented code means lower mainteance :) 我知道它可以写成param.SqlDbType中的开关,但这是更短的方式,更短的注释代码意味着更低的维护:)

Unfortunately, as far as I know this mapping is not exposed in code inside the .NET Framework. 不幸的是,据我所知,这个映射没有在.NET Framework中的代码中公开。 I've looked through the .NET Framework reference source before for this, and found that inside the .NET code there's a lot of long per-type switch statements, just like the ones you're trying to avoid, but none of them seem to be exposed externally. 我之前已经浏览过.NET Framework参考源,发现在.NET代码中有很多很长的每类型切换语句,就像你想要避免的那样,但它们似乎都没有暴露在外面。

If you really just want to map from SqlTypes to the most likley .NET type, I think your best bet is to simply turn the mapping table in the MSDN docs into code. 如果你真的只想从SqlTypes映射到最可能的.NET类型,我认为你最好的办法是简单地将MSDN文档中的映射表转换为代码。 Note that the table on MSDN has (at least) two errors: #1: there is no .NET type called "DateTime2" (I used DateTime) and there is also no type called "Xml" (I used SqlXml). 请注意,MSDN上的表有(至少)两个错误:#1:没有名为“DateTime2”的.NET类型(我使用DateTime),也没有名为“Xml”的类型(我使用的是SqlXml)。

Anyway, here's the mapping I've been using-- using a Dictionary instead of a switch for ease of access without a separate method. 无论如何,这是我一直在使用的映射 - 使用Dictionary而不是开关,以便于访问而无需单独的方法。

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) }, 
};

Note that one thing you'll need to watch out for is size/precision-- some SQL types (eg varchar ) have size limits, while .NET types (eg string ) don't. 请注意,您需要注意的一件事是大小/精度 - 某些SQL类型(例如varchar )具有大小限制,而.NET类型(例如string )则没有。 So being able to know the most-likely .NET type is not really enough... if you're using this to, for example, drive validation rules, you also need to be able to prevent users from entering invalid (eg too large) values by knowing more about the parameter, like the precision. 因此,能够了解最可能的.NET类型并不足够......如果您正在使用它,例如,驱动器验证规则,您还需要能够阻止用户输入无效(例如,太大) )通过了解更多有关参数的值,如精度。 Note that, if you look inside the SqlClient source, they use special code to handle cases like setting the precision of a Decimal type from the corresponding SQL precision. 请注意,如果查看SqlClient源代码,它们会使用特殊代码来处理从相应的SQL精度设置Decimal类型的精度等情况。

Note that if the only reason you need the .NET type is to be able to stuff data into a stored proc parameter, you might want to try simply using ToString() on all your .NET values, stuffing a string into the Value property of the SqlParameter, and seeing if the framework will do the conversion/parsing for you. 请注意,如果您需要.NET类型的唯一原因是能够将数据填充到存储的proc参数中,您可能想要尝试在所有.NET值上使用ToString(),将字符串填充到Value属性中SqlParameter,看看框架是否会为你做转换/解析。 For example, for an XML or Date parameter you might be able to get away with sending a string instead. 例如,对于XML或Date参数,您可以通过发送字符串来逃避。

Also, instead of using reflection to find a Parse() method on each type, since there's a known (and small) list of types, you can get better performance by using strongly-typed parsing code for each, like the code below. 此外,不是使用反射来查找每种类型的Parse()方法,因为有一个已知(和小)的类型列表,您可以通过为每个类型使用强类型解析代码来获得更好的性能,如下面的代码。 (Note that several types (eg SqlDbType.Udt) don't necessarily have an obvious parser method-- you'll need to figure out how you want to handle those.) (请注意,有几种类型(例如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 }, 
};

The code to use above is pretty easy, eg : 上面使用的代码非常简单,例如:

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

No one else seems to want to tell you, but what you're doing is probably not the best way to do it. 似乎没有人想告诉你,但你所做的可能不是最好的方法。

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

You're saying that you're given a string value, which you know has to be assigned to a parameter, and you want to stuff that value in there any way that it can fit? 你是说你得到了一个字符串值,你知道它必须分配给一个参数,并且你想以任何方式填充该值吗?

Please consider why you are doing this. 请考虑一下为什么这样做。 You are making the assumption that the following code is right: 您假设以下代码是正确的:

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

There's no clear reason why this is better than: 没有明确的理由说明为什么这比以下更好:

param.Value = value;

But since you want to do it, it seems safe to assume that you have tried this and found that your real problem is that value isn't the right type for the parameter. 但是既然你想要这样做,那么假设你已经尝试过这个并发现你真正的问题是value不是参数的正确类型似乎是安全的。 Thus you want a magical fix that you can run which will always make sure that value is the right type. 因此,您需要一个可以运行的神奇修复程序,它始终确保该value是正确的类型。 What you really want is likely: 您真正想要的可能是:

SetParam(param, value);

Where this function stuffs the value into the parameter. 此函数将值填充到参数中。 This actually makes things a bit easier if value is not simply of type object as you say, but has a real type (like int or string ). 如果value不是像你说的那样只是类型object ,这实际上会使事情变得容易一些,但它有一个真实的类型(如intstring )。 This is because you can use method overloading like SetParam(SqlParam param, int value) or generics to infer the value type SetParam<T>(SqlParam param, T value) . 这是因为你可以像SetParam(SqlParam param, int value)或泛型一样使用方法重载来推断值类型SetParam<T>(SqlParam param, T value)

So we know the function you want, what we don't know is why. 所以我们知道你想要的功能,我们不知道为什么。 In most reasonable scenarios you have an idea of the types of the values, and you also have an idea of the type of the parameter. 在大多数合理的场景中,您可以了解值的类型,并且还可以了解参数的类型。 You are asking for a way to cram a value that doesn't match a parameter into a parameter that you don't understand. 您正在寻找一种方法,将与参数不匹配的值塞入您不理解的参数中。

There are two main reasons I can think of for this request: 我可以为这个请求考虑两个主要原因:

  1. You in reality know that the types are compatible, and are looking for a general way to do this to avoid writing a lot of code. 实际上,您知道类型是兼容的,并且正在寻找一种通用的方法来避免编写大量代码。 So you know that you are trying to assign a long to a parameter that is a SqlInt , and are relying on string conversions to get you past the type safety issues. 所以你知道你正在尝试为一个SqlInt参数分配一个long ,并且依赖于字符串转换来让你超越类型安全问题。

  2. You don't really understand the code that you are using and are trying to patch in a fix to get something working. 您并不真正理解您正在使用的代码,并且正在尝试修补修补程序以使某些工作正常。

It's really important to be honest with yourself about which case you are in. If you are in the first case, then you can write a method like SetParam that I described above fairly easily. 真正重要的是要诚实地告诉自己你处于哪种情况。如果你是第一种情况,那么你可以写一个像我上面描述的SetParam这样的方法。 You will have to write a switch statement (or like the best answer above, a Dictionary lookup). 您必须编写一个switch语句(或者像上面的最佳答案,Dictionary查找)。 You are going to have to lose precision (casting a long to an int doesn't work for large numbers, but neither will your Parse) but it will work. 你将不得不失去精确度(将一个long转换为int对大数字不起作用,但你的Parse也不行)但它会起作用。

If you're in the second case, stop for a minute. 如果你是第二种情况,请停一分钟。 Recognize that you are setting yourself up for more bugs in the future (because converting to and from string will not solve the problems you have of not understanding the types). 认识到你将来会为自己设置更多错误(因为转换为字符串和从字符串转换不会解决你不了解类型的问题)。 You know that you need help, which is why you are on Stack Overflow and offering a bounty for help, and you are dealing with a codebase that you don't understand. 您知道自己需要帮助,这就是为什么您使用Stack Overflow并提供帮助的原因,并且您正在处理您不理解的代码库。 I can tell right now from your question that you are going to dig yourself a deeper hole than you realize if this is your situation, because you have already refused the best answer (to do a switch statement based on parameter type) for no strong reason. 我现在可以从你的问题中告诉你,如果这是你的情况,你将要挖掘自己更深的洞,因为你已经拒绝了最好的答案(基于参数类型进行切换声明)没有充分的理由。

So, if you are in the second case, the thing that is going to help you most is not an answer from Stack Overflow, unless you are willing to describe your real problem more completely. 因此,如果您处于第二种情况,那么最有助于您的事情不是Stack Overflow的答案,除非您愿意更完整地描述您的真实问题。 What will help you is understanding where the values are coming from (is it UI? Is it a different subsystem, which rules do they follow? Is there a reason the types don't match?) and where they are going (what is the definition of the stored procedure you are calling? What are the parameter types defined as?). 什么会帮助你理解价值的来源(是UI吗?它是一个不同的子系统,它们遵循哪些规则?这些类型不匹配的原因是什么?)以及它们的去向(什么是您正在调用的存储过程的定义?定义为什么参数类型?)。 I imagine you probably don't even need to go into SQL to find this, as whoever gave you the SqlParam probably already has defined it properly for you. 我想你甚至可能不需要进入SQL来找到它,因为无论谁给你SqlParam可能已经为你正确定义了它。 If you defined it, you do indeed need to go to the SQL to figure it out, immediately. 如果你定义了它,你确实需要立即转到SQL来解决它。

i think you're missing a step here. 我想你在这里错过了一步。 The first thing you need to do is query the database for the definition of the stored proc via a select call and inner join to the sys objects table or by using a management wrapper. 您需要做的第一件事是通过select调用和内部连接查询数据库以获取存储过程的定义,或者使用管理包装器。 Then you can "infer" the types of parameters based upon the returned information. 然后,您可以根据返回的信息“推断”参数类型。

Here is an MSO lin k to get you started 这是一个MSO lin k,可以帮助您入门

And an example of how to query the database structure directly 以及如何直接查询数据库结构的示例

If you run the sql from the second example against your database you'll see exactly what's up: 如果您针对数据库运行第二个示例中的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

You cannot necessarily implicitly and accurately extract the correct .NET CTS ("underlying") type because it might change depending on the value in the parameter - the SqlParameter's .DbType and .SqlDbType are mutable and explicitly settable by the programmer (or the code-generation engine) In the case of an output parameter, the .DbType/.SqlDbType can be wrong even after having been right for a time, for example if the value underneath that comes back suddenly is different than expected in .NET terms. 您不一定能隐式准确地提取正确的.NET CTS(“底层”)类型,因为它可能会根据参数中的值而改变 - SqlParameter的.DbType和.SqlDbType是可变的,并且可由程序员明确设置(或代码 -生成引擎)在输出参数的情况下,.DbType / .SqlDbType即使在正确一段时间后也可能是错误的,例如,如果突然返回的值突然与.NET术语中的预期不同。 The values are driven by the data store and .NET SqlParameter copes as best it can with its explicit types. 这些值由数据存储驱动,.NET SqlParameter尽可能地使用其显式类型进行处理。 The data value of the SqlParameter should be considered weakly typed in .NET terms (evidenced by the parm.Value property's System.Object return value). 应该认为SqlParameter的数据值在.NET术语中是弱类型的(由parm.Value属性的System.Object返回值证明)。

Your best bet is 你最好的选择是

  1. Use one of the mapping methods outlined by other posters - of course that has its own implicit assumption that the SQL Parameter type will always be correct for the data in it. 使用其他海报概述的映射方法之一 - 当然,它有自己的隐含假设,即SQL参数类型对于其中的数据始终是正确的。
  2. possibly test the value coming back from the output parameter and assume successive values are of the same kind. 可能会测试从输出参数返回的值,并假设连续值属于同一类型。 Of course that's really up to the database. 当然,这完全取决于数据库。
  3. Find another strategy instead of relying on the Microsoft Sql namespace - you might be much happier in the future. 找到另一种策略而不是依赖于Microsoft Sql命名空间 - 您将来可能会更加快乐。

Testing the value for a .NET CTS type would look something like System.Type t = paramInstance.Value.GetType(); 测试.NET CTS类型的值看起来类似于System.Type t = paramInstance.Value.GetType(); Null will cause an exception. Null会导致异常。 You'd still need to cast it appropriately using a switch or if/else, unless you pull out some fancy reflection techniques. 你还需要使用开关或if / else来适当地投射它,除非你拿出一些花哨的反射技术。

If you can resolve to the correct SqlType, Reflection will get you the explicit cast to a .NET type. 如果您可以解析为正确的SqlType,Reflection将为您提供.NET类型的显式强制转换。 The return value would be the underlying System.Type. 返回值将是基础System.Type。 Caching the result should make up for the perf on 1st lookup. 缓存结果应该弥补第一次查找时的性能。

Take a look at what they do in linq to sql t4 , it seem to work nicely. 看看他们在linq到sql t4中做了什么,它看起来效果很好。

You might be able to find out what you need by looking at the code. 您可以通过查看代码找到所需内容。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何检索从sqlstoredprocedure返回的单个值? - How can I retrieve single value returned from sql storedprocedure? 给定参数的ParameterInfo时,如何检查服务方法的参数类型是否为输出类型? - How do I check if a service method's parameter type is an output type given the parameter's ParameterInfo? C#-SQL:如何执行一批StoredProcedure? - C#-SQL: How to execute a batch of StoredProcedure? 在 ASP.NET-Core 2.2 中检索给定类型的声明集合 - Retrieve claims collection of a given type in ASP.NET-Core 2.2 存储CommandType时如何使用IDbCommandInterceptor修改SQL - How to use IDbCommandInterceptor to modify SQL when CommandType is StoredProcedure 将字符串与类相关联,以便在将类作为通用类型参数给出的情况下检索字符串 - associate a string with a class so that I can retrieve the string if class is given as generic type parameter SQL选择多个值StoredProcedure - SQL Select Multiple Values StoredProcedure 具有非必需参数的SQL StoredProcedure - SQL StoredProcedure with non required parameters 在实体框架中将ObjectResult与输入参数一起用于StoredProcedure的正确方法是什么? (输出映射到复杂类型属性) - What's the correct way to use ObjectResult with input parameters to StoredProcedure in Entity Framework? (Output mapped to Complex Type Property) 如何获取给定方法的泛型参数的类型 - How do I get the Type of a generic parameter for a given method
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM