.NET System Type to SqlDbType

时间:2016-03-02 10:57:13

标签: c# ado.net system.data

I was looking for a smart conversion between .Net System.Type and SqlDbType. What I found it was the following idea:

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

The code above is not really nice and is a code smell, which is why I wrote the following, naïve, not smart, but useful function, based on 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)];
     }

Does someone have idea of how to get the same result in a cleaner, better and nice way?

4 个答案:

答案 0 :(得分:14)

Your approach is a good start, but populating that dictionary should only be done once, as Ian says in a comment.

There is a GIST here that is based on the same idea, although it doesn't convert between the same sets of types: https://gist.github.com/abrahamjp/858392

Caveat

I have a working example below, but you need to be aware that this approach does have a few problems. For example:

  • For a string, how do you pick the correct one between Char, NChar, VarChar, NVarChar, Text or NText (or even Xml, maybe)?
  • And for blobs like byte[], should you use Binary, VarBinary or Image?
  • For decimal, float and double, should you go for Decimal, Float, Money, SmallMoney or Real?
  • For a DateTime, do you need DateTime2, DateTimeOffset, DateTime, or SmallDateTime?
  • Are you using Nullable types, like int?? Those should most likely give the same SqlDbType as the underlying type.

Also, just providing a Type tells you nothing of other constraints, like field size and precision. Making the right decision is also about how the data is used in your application and how it is stored in the database.

The best thing to do is really to let an ORM do this for you.

Code

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

And this is how you would use it:

var sqlDbType = SqlHelper.GetDbType<string>();
// or:
var sqlDbType = SqlHelper.GetDbType(typeof(DateTime?));
// or:
var sqlDbType = SqlHelper.GetDbType(property.PropertyType);

答案 1 :(得分:4)

看来这种查找表已经可用,虽然不在System.Data(或.Object.Type),而是在System.Web中。

项目 - &gt;添加参考 - &gt; System.Web - &gt;行

然后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;
}

答案 2 :(得分:0)

编辑:我在想,这适用于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属性,并且不需要反射。
它并不完美,但您不必手动创建,维护或填充字典。您只需在现有的dict中查找类型,如果它不存在,请使用upper方法自动添加映射。 几乎是自动生成的 还要处理SQL Server将来可能会收到的任何新类型。

答案 3 :(得分:0)

我的办公室同事给了我一个尝试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