Тип системы .NET в SqlDbType

Я искал разумное преобразование между .Net System.Type и SqlDbType. То, что я нашел, было следующей идеей:

private static SqlDbType TypeToSqlDbType(Type t)
{
    String name = t.Name;
    SqlDbType val = SqlDbType.VarChar; // default value
    try
    {
        if (name.Contains("16") || name.Contains("32") || name.Contains("64"))
            {
                name = name.Substring(0, name.Length - 2);
            }
            val = (SqlDbType)Enum.Parse(typeof(SqlDbType), name, true);
        }
        catch (Exception)
        {
            // add error handling to suit your taste
        }

        return val;
    }

Приведенный выше код не очень хорош и является запахом кода, поэтому я написал следующую, наивную, не умную, но полезную функцию, основанную на https://msdn.microsoft.com/en-us/library/cc716729(v=vs.110).aspx :

   public static SqlDbType ConvertiTipo(Type giveType)
    {
       var typeMap = new Dictionary<Type, SqlDbType>();

        typeMap[typeof(string)] = SqlDbType.NVarChar;
        typeMap[typeof(char[])] = SqlDbType.NVarChar;
        typeMap[typeof(int)] = SqlDbType.Int;
        typeMap[typeof(Int32)] = SqlDbType.Int;
        typeMap[typeof(Int16)] = SqlDbType.SmallInt;
        typeMap[typeof(Int64)] = SqlDbType.BigInt;
        typeMap[typeof(Byte[])] = SqlDbType.VarBinary;
        typeMap[typeof(Boolean)] = SqlDbType.Bit;
        typeMap[typeof(DateTime)] = SqlDbType.DateTime2;
        typeMap[typeof(DateTimeOffset)] = SqlDbType.DateTimeOffset;
        typeMap[typeof(Decimal)] = SqlDbType.Decimal;
        typeMap[typeof(Double)] = SqlDbType.Float;
        typeMap[typeof(Decimal)] = SqlDbType.Money;
        typeMap[typeof(Byte)] = SqlDbType.TinyInt;
        typeMap[typeof(TimeSpan)] = SqlDbType.Time;

        return typeMap[(giveType)];
     }

Кто-нибудь знает, как получить тот же результат более чистым, лучшим и приятным способом?


person Simone Salvo    schedule 02.03.2016    source источник
comment
Преобразование словаря в порядке. Делается один раз в жизни. :) (меньше изменений)   -  person Ian    schedule 02.03.2016
comment
Если мой ответ помог вам, пожалуйста, отметьте его как выбранный ответ. :)   -  person Anders Marzi Tornblad    schedule 19.04.2017
comment
Извините за ультрапоздний ответ! Большое спасибо!   -  person Simone Salvo    schedule 08.11.2018


Ответы (4)


Ваш подход — хорошее начало, но заполнять этот словарь нужно только один раз, как говорит Ян в комментарии.

Здесь есть GIST, основанный на той же идее, хотя и не конвертирующий между одними и теми же наборами типов: https://gist.github.com/abrahamjp/858392

Осторожно

У меня есть приведенный ниже рабочий пример, но вы должны знать, что этот подход есть несколько проблем. Например:

  • Как правильно выбрать string из Char, NChar, VarChar, NVarChar, Text или NText (или даже Xml, возможно)?
  • А для больших двоичных объектов, таких как byte[], следует ли использовать Binary, VarBinary или Image?
  • Что выбрать для decimal, float и double Decimal, Float, Money, SmallMoney или Real?
  • Для DateTime вам нужны DateTime2, DateTimeOffset, DateTime или SmallDateTime?
  • Используете ли вы типы Nullable, например int?? Скорее всего, они должны давать тот же SqlDbType, что и базовый тип.

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

Лучше всего позволить ORM сделать это за вас.

Код

public static class SqlHelper
{
    private static Dictionary<Type, SqlDbType> typeMap;

    // Create and populate the dictionary in the static constructor
    static SqlHelper()
    {
        typeMap = new Dictionary<Type, SqlDbType>();

        typeMap[typeof(string)]         = SqlDbType.NVarChar;
        typeMap[typeof(char[])]         = SqlDbType.NVarChar;
        typeMap[typeof(byte)]           = SqlDbType.TinyInt;
        typeMap[typeof(short)]          = SqlDbType.SmallInt;
        typeMap[typeof(int)]            = SqlDbType.Int;
        typeMap[typeof(long)]           = SqlDbType.BigInt;
        typeMap[typeof(byte[])]         = SqlDbType.Image;
        typeMap[typeof(bool)]           = SqlDbType.Bit;
        typeMap[typeof(DateTime)]       = SqlDbType.DateTime2;
        typeMap[typeof(DateTimeOffset)] = SqlDbType.DateTimeOffset;
        typeMap[typeof(decimal)]        = SqlDbType.Money;
        typeMap[typeof(float)]          = SqlDbType.Real;
        typeMap[typeof(double)]         = SqlDbType.Float;
        typeMap[typeof(TimeSpan)]       = SqlDbType.Time;
        /* ... and so on ... */
    }

    // Non-generic argument-based method
    public static SqlDbType GetDbType(Type giveType)
    {
        // Allow nullable types to be handled
        giveType = Nullable.GetUnderlyingType(giveType) ?? giveType;

        if (typeMap.ContainsKey(giveType))
        {
            return typeMap[giveType];
        }

        throw new ArgumentException($"{giveType.FullName} is not a supported .NET class");
    }

    // Generic version
    public static SqlDbType GetDbType<T>()
    {
        return GetDbType(typeof(T));
    }
}

И вот как вы будете его использовать:

var sqlDbType = SqlHelper.GetDbType<string>();
// or:
var sqlDbType = SqlHelper.GetDbType(typeof(DateTime?));
// or:
var sqlDbType = SqlHelper.GetDbType(property.PropertyType);
person Anders Marzi Tornblad    schedule 02.03.2016
comment
Выглядит неплохо! Я бы только добавил проверку, чтобы увидеть, существует ли тип (ContainsKey) в словаре, и если нет, выдать NotSupportedException (или пользовательское исключение) с вашим собственным подробным сообщением вместо KeyNotFoundException по умолчанию. Это может упростить устранение неполадок позже, если когда-либо будет передан неподдерживаемый тип. - person Igor; 02.03.2016
comment
Спасибо за совет. Я отредактировал ответ, чтобы бросить ArgumentException, так как NotSupportedException не предназначен для такого рода вещей. - person Anders Marzi Tornblad; 02.03.2016

Похоже, что такая таблица поиска уже доступна, хотя и не в System.Data (или .Object, или .Type), а скорее в System.Web.

Проект -> Добавить ссылку -> System.Web -> ОК

Затем https://msdn.microsoft.com/en-us/library/system.data.sqldbtype(v=vs.110).aspx также говорит

При задании параметров команды SqlDbType и DbType связаны. Поэтому установка DbType изменяет SqlDbType на поддерживающий SqlDbType.

Так что теоретически должно работать ;)

using Microsoft.SqlServer.Server; // SqlDataRecord and SqlMetaData
using System;
using System.Collections; // IEnumerator and IEnumerable
using System.Collections.Generic; // general IEnumerable and IEnumerator
using System.Data; // DataTable and SqlDataType
using System.Data.SqlClient; // SqlConnection, SqlCommand, and SqlParameter
using System.Web.UI.WebControls; // for Parameters.Convert... functions

private static SqlDbType TypeToSqlDbType(Type t) {
    DbType dbtc = Parameters.ConvertTypeCodeToDbType(t.GetTypeCodeImpl());
    SqlParameter sp = new SqlParameter();
    // DbParameter dp = new DbParameter();
    // dp.DbType = dbtc;
    sp.DbType = dbtc;
    return sp.SqlDbType;
}
person mpag    schedule 12.12.2017
comment
Спасибо, это идеальная информация для того, что мне нужно! - person SnookerC; 08.06.2018

Мой коллега по офису дал мне идею попробовать свойство SqlParameter:

Func<Object, SqlDbType> getSqlType = val => new SqlParameter("Test", val).SqlDbType;
Func<Type, SqlDbType> getSqlType2 = type => new SqlParameter("Test", type.IsValueType?Activator.CreateInstance(type):null).SqlDbType;

//returns nvarchar...
Object obj = "valueToTest";
getSqlType(obj).Dump();
getSqlType2(typeof(String)).Dump();

//returns int...
obj = 4;
getSqlType(obj).Dump();
getSqlType2(typeof(Int32)).Dump();

//returns bigint...
obj = Int64.MaxValue;
getSqlType(obj).Dump();
getSqlType2(typeof(Int64)).Dump();

https://dotnetfiddle.net/8heM4H

person b_levitt    schedule 11.03.2019
comment
Обратите внимание, что это не обрабатывает типы, допускающие значение NULL. - person Ian Kemp; 03.06.2020
comment
Каким образом? Я привел некоторые из литералов (например, (int?) 4), и, похоже, это работает - все еще возвращает int как тип данных sql. Помните, что это получение эквивалентного типа данных в sql. В отличие от .net, где у вас действительно есть два типа, допустимость значений NULL присуща типу данных, а затем запрещается с помощью ограничения. - person b_levitt; 03.06.2020

Изменить: я думал об этом, и это работает для типов System.Data.SqlTypes. Я оставлю это здесь на всякий случай, если это поможет кому-то в будущем.

Я делаю что-то вроде этого:

object objDbValue = DbReader.GetValue(columnIndex);
Type sqlType = DbReader.GetFieldType(columnIndex);
Type clrType = null;

if (sqlType.Name.StartsWith("Sql"))
{   
    var objClrValue = objDbValue.GetType()
                                .GetProperty("Value")
                                .GetValue(objDbValue, null);
    clrType = objClrValue.GetType();
}

Поскольку у каждого SqlDbType есть свойство .Value, которое является фактическим базовым типом CLR, я использую отражение, чтобы получить его. Очень жаль, что у SqlDbType нет интерфейса, который бы содержал это свойство .Value, и отражение не потребовалось бы.
Это не идеально, но вам не нужно вручную создавать, поддерживать или заполнять словарь. Вы можете просто найти тип в существующем словаре, и если он не существует, используйте верхний метод для автоматического добавления сопоставления. Генерируется практически автоматически.
Также позаботится о любых новых типах, которые SQL Server может получить в будущем.

person Mladen Prajdic    schedule 02.03.2016
comment
Отредактировано: не уверен, куда делся комментарий, на который я отвечал. ах, вы правы, для другого направления у меня нет лучшего ответа, чем предварительно заполненный словарь. Хотя обычно вариант использования — от типа sql к типу clr, потому что один тип sql может сопоставляться с несколькими типами clr. - person Mladen Prajdic; 02.03.2016
comment
Похоже на SqlDbType — это перечисление, поэтому я не уверен, как оно содержит дополнительную информацию о типе CLR. Также для создания запросов это не сработает, только для перевода результатов запроса в правильный тип CLR. - person Igor; 02.03.2016
comment
Извините, я сразу удалил свой комментарий, потому что хотел немного переосмыслить. Мой первоначальный комментарий был Разве это не идет в противоположном направлении?. Теперь я больше с @Igor, так как желаемое SqlDbType является перечислением. - person Anders Marzi Tornblad; 02.03.2016
comment
вы правы, я постоянно думал о пространстве имен System.Data.SqlTypes. msdn.microsoft.com/en -us/library/ плохо. - person Mladen Prajdic; 02.03.2016