Как получить .NET тип заданного параметра StoredProcedure в SQL?

Я создаю "общий" обертку над процедурами 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, но это более короткий путь, а более короткий код комментария означает более низкое обслуживание:)

Ответ 1

К сожалению, насколько я знаю, это сопоставление не отображается в коде внутри .NET Framework. Я просмотрел исходный исходный код .NET Framework для этого и обнаружил, что внутри кода .NET есть много длинных операторов переключения типа, как и те, которые вы пытаетесь избежать, но ни один из них не выглядит подвергаться внешнему воздействию.

Если вы действительно хотите отобразить с SqlTypes на самый похожий тип .NET, я думаю, что ваш лучший выбор - просто повернуть таблицу отображения в MSDN docs в код. Обратите внимание, что в таблице MSDN есть (по крайней мере) две ошибки: # 1: нет типа .NET, называемого "DateTime2" (я использовал 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.

Обратите внимание, что если единственная причина, по которой вам нужен тип .NET, - это возможность хранить данные в хранимом параметре proc, вы можете попробовать просто использовать ToString() для всех ваших значений .NET, набивая строку в Значение свойства SqlParameter, и посмотрите, будет ли структура выполнять преобразование/синтаксический анализ для вас. Например, для параметра 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, как вы говорите, но имеет реальный тип (например, int или string). Это связано с тем, что вы можете использовать перегрузку метода, например, SetParam(SqlParam param, int value) или generics, чтобы вывести тип значения SetParam<T>(SqlParam param, T value).

Итак, мы знаем, какую функцию вы хотите, поэтому мы не знаем, почему. В наиболее разумных сценариях вы имеете представление о типах значений, а также имеете представление о типе параметра. Вы запрашиваете способ вставить значение, которое не соответствует параметру, в параметр, который вы не понимаете.

Для этого запроса можно выделить две основные причины:

  • В действительности вы знаете, что типы совместимы и ищут общий способ сделать это, чтобы избежать написания большого количества кода. Таким образом, вы знаете, что вы пытаетесь присвоить long параметру, который является SqlInt, и полагаетесь на преобразование строк, чтобы избежать проблем с безопасностью типа.

  • Вы действительно не понимаете код, который используете, и пытаетесь исправить исправление, чтобы что-то работать.

Очень важно быть честным с самим собой, в каком случае вы находитесь. Если вы в первом случае, то вы можете написать такой метод, как SetParam, который я описал выше довольно легко. Вам нужно будет написать оператор switch (или, как лучший ответ выше, поиск в Словаре). Вам придется потерять точность (приведение длинного к int не работает для больших чисел, но и ваш Parse), но он будет работать.

Если вы во втором случае, остановитесь на минуту. Признайте, что вы настроите себя на большее количество ошибок в будущем (потому что преобразование в строку и из нее не решит проблемы, которые вы не понимаете для типов). Вы знаете, что вам нужна помощь, поэтому вы находитесь в Qaru и предлагаете награду за помощь, и вы имеете дело с кодовой базой, которую вы не понимаете. Я могу сказать прямо сейчас из вашего вопроса, что вы собираетесь копать себе более глубокое отверстие, чем вы понимаете, если это ваша ситуация, потому что вы уже отказались от лучшего ответа (сделать оператор switch на основе типа параметра) без какой-либо веской причины.

Итак, если вы во втором случае, то, что вам больше всего поможет, - это не ответ от Stack Overflow, если вы не желаете более подробно описать свою реальную проблему. Что поможет вам понять, откуда взялись ценности (это UI? Является ли это другой подсистемой, какие правила они следуют? Есть ли причина, по которой типы не совпадают?) И куда они идут (что такое определение хранимой процедуры, которую вы вызываете? Каковы типы параметров, определенные как?). Я предполагаю, что вам, вероятно, даже не нужно идти в SQL, чтобы найти это, так как тот, кто дал вам SqlParam, вероятно, уже правильно определил его для вас. Если вы определили его, вам действительно нужно сразу перейти к SQL, чтобы понять это.

Ответ 3

Думаю, вам здесь не хватает шага. Первое, что вам нужно сделать, - запросить базу данных для определения хранимой процедуры через вызов select и внутреннее соединение в таблице объектов sys или с помощью обертки управления. Затем вы можете "вывести" типы параметров на основе возвращаемой информации.

Вот 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

Ответ 4

Вы не можете неявно и точно извлекать правильный тип .NET CTS ( "базовый" ), потому что он может меняться в зависимости от значения параметра - параметры SqlParameter.DbType и .SqlDbType являются изменяемыми и явно установленным программистом (или движком генерации кода). В случае выходного параметра .DbType/.SqlDbType может быть неправильным даже после того, как он был прав на какое-то время, например, если значение под ним возвращается внезапно чем ожидалось в .NET. Значения управляются хранилищем данных, а .NET SqlParameter справляется как можно лучше с его явными типами. Значение данных SqlParameter следует считать слабо типизированным в .NET-терминах (о чем свидетельствует свойство return parm.Value System.Object).

Лучше всего

  • Используйте один из методов сопоставления, описанный другими плакатами - конечно, у него есть свое собственное неявное предположение, что тип параметра SQL всегда будет правильным для данных в нем.
  • возможно, проверьте значение, возвращаемое из выходного параметра, и предположим, что последовательные значения имеют один и тот же вид. Конечно, это действительно до базы данных.
  • Найдите другую стратегию вместо того, чтобы полагаться на пространство имен Microsoft Sql - в будущем вы можете быть намного счастливее.

Тестирование значения для типа .NET CTS будет выглядеть примерно так: System.Type t = paramInstance.Value.GetType(); Null вызовет исключение. Вам все равно нужно будет использовать его соответствующим образом с помощью переключателя или if/else, если вы не вытащите некоторые причудливые методы отражения.

Ответ 5

Если вы можете решить правильный SqlType, Reflection получит явное приведение к типу .NET. Возвращаемое значение будет основным типом System.Type. Кэширование результата должно компенсировать перфоманс при первом поиске.

Ответ 6

Посмотрите, что они делают в linq to sql t4, похоже, что он работает хорошо.

Возможно, вы сможете узнать, что вам нужно, посмотрев код.