c# - Tipo de sistema.NET a SqlDbType
ado.net system.data (2)
Estaba buscando una conversión inteligente entre .Net System.Type y SqlDbType. Lo que encontré fue la siguiente 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;
}
El código anterior no es realmente agradable y es un olor a código, por lo que escribí la siguiente, ingenua, no inteligente, pero útil función, basada en 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)];
}
¿Alguien tiene idea de cómo obtener el mismo resultado de una manera más limpia, mejor y agradable?
Editar: estaba pensando y esto funciona para los tipos System.Data.SqlTypes. Lo dejaré aquí solo en caso de que ayude a alguien en el futuro.
Yo hago algo como esto:
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();
}
Como cada SqlDbType tiene una propiedad .Value que es el tipo de CLR subyacente real, yo uso el reflejo para obtenerlo. Es una lástima que SqlDbType no tenga alguna interfaz que pueda contener esta propiedad .Value y la reflexión no sería necesaria.
No es perfecto, pero no es necesario crear, mantener o completar manualmente un diccionario. Puede buscar un tipo en un diccionario existente y, si no existe, utilice el método superior para agregar el mapa automáticamente. Casi autogenerado.
También se ocupa de los tipos nuevos que SQL Server pueda recibir en el futuro.
Su enfoque es un buen comienzo, pero poblar ese diccionario solo debe hacerse una vez , como dice Ian en un comentario.
Aquí hay un GIST que se basa en la misma idea, aunque no convierte entre los mismos tipos de tipos: https://gist.github.com/abrahamjp/858392
Advertencia
Tengo un ejemplo de trabajo a continuación, pero debe tener en cuenta que este enfoque tiene algunos problemas. Por ejemplo:
- Para una
string
, ¿cómo elegir la correcta entreChar
,NChar
,VarChar
,NVarChar
,Text
oNText
(o inclusoXml
, tal vez) ? - Y para blobs como
byte[]
, ¿deberías usarBinary
,VarBinary
oImage
? - Para
decimal
,float
ydouble
, ¿deberíaSmallMoney
Decimal
,Float
,Money
,SmallMoney
oReal
? - Para un
DateTime
, ¿necesitaDateTime2
,DateTimeOffset
,DateTime
oSmallDateTime
? - ¿Estás usando tipos
Nullable
, comoint?
? Es muy probable que estos den el mismo tipo deSqlDbType
como el tipo subyacente.
Además, el solo hecho de proporcionar un Type
le dice nada sobre otras restricciones, como el tamaño del campo y la precisión. Tomar la decisión correcta también se trata de cómo se usan los datos en su aplicación y cómo se almacenan en la base de datos.
Lo mejor que puedes hacer es dejar que un ORM haga esto por ti.
Código
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));
}
}
Y así es como lo usarías:
var sqlDbType = SqlHelper.GetDbType<string>();
// or:
var sqlDbType = SqlHelper.GetDbType(typeof(DateTime?));
// or:
var sqlDbType = SqlHelper.GetDbType(property.PropertyType);