Unfortunately, as far as I know this mapping is not exposed in code inside the .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.
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. 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).
Anyway, here's the mapping I've been using-- using a Dictionary instead of a switch for ease of access without a separate method.
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 (e.g. varchar
) have size limits, while .NET types (e.g. string
) don't. 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 (e.g. too large) values by knowing more about the parameter, like the precision. 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.
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. For example, for an XML or Date parameter you might be able to get away with sending a string instead.
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. (Note that several types (e.g. SqlDbType.Udt) don't necessarily have an obvious parser method-- you'll need to figure out how you want to handle those.)
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, e.g. :
string valueToSet = "1234";
SqlParameter p = new SqlParameter();
p.SqlDbType = System.Data.SqlDbType.Int;
p.Value = TypeMapper[p.SqlDbType](valueToSet);