2009-10-20 15 views
11

Estoy creando una envoltura 'genérica' sobre los procedimientos de SQL, y puedo resolver todos los nombres de los parámetros requeridos y los tipos de sql, pero ¿hay alguna forma de obtener su tipo 'subyacente' de .NET?¿Cómo recuperar el tipo de .NET del parámetro StoredProcedure dado en SQL?

Mi objetivo es hacer algo como:

SqlParameter param; 
object value; 
object correctParam = param.GetNETType().GetMethod("Parse", 
    new Type[] { typeof(string) }).Invoke(value.ToString()); 
param.Value = correctParam; 

Dónde GetNETType es lo que necesito. Sé que se puede escribir como interruptor dentro de param.SqlDbType, pero esta es una forma más corta, y el código comentado más corto significa menor mantenimiento :)

+0

¿Qué estás tratando exactamente de hacer? ¿Estoy en lo cierto cuando leí que tienes una colección de SqlParameters y tienes una colección de valores .net? ¿Y desea "convertir" su colección de valores a las propiedades SqlParameter.Value? Ejemplo: SqlParamaters [0].Valor = MagicConvert (Valores [0]); – Zenuka

Respuesta

12

Desafortunadamente, hasta donde sé, esta asignación no está expuesta en el código dentro de .NET Framework. Antes busqué en el origen de referencia de .NET Framework y descubrí que dentro del código .NET hay muchas sentencias de conmutación largas por tipo, como las que intenta evitar, pero ninguna de ellas parece estar expuesto externamente

Si realmente desea hacer un mapa de SqlTypes al tipo de .NET más parecido, creo que su mejor opción es simplemente convertir la tabla de asignación in the MSDN docs en el código. Tenga en cuenta que la tabla en MSDN tiene (al menos) dos errores: # 1: no hay tipo .NET llamado "DateTime2" (utilicé DateTime) y tampoco hay ningún tipo llamado "Xml" (utilicé SqlXml).

De todos modos, aquí está el mapeo que he estado usando-- usando un diccionario en lugar de un interruptor para facilitar el acceso sin un método diferente.

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

Tenga en cuenta que una cosa que tendrá que tener en cuenta es el tamaño/precision-- algunos tipos SQL (por ejemplo varchar) tienen límites de tamaño, mientras que los tipos de .NET (por ejemplo string) no lo hacen. Por lo tanto, no es suficiente poder conocer el tipo de .NET más probable ... si está utilizando esto para, por ejemplo, reglas de validación de unidades, también debe ser capaz de evitar que los usuarios entren inválidos (por ejemplo, demasiado grande).) valores al saber más sobre el parámetro, como la precisión. Tenga en cuenta que, si mira dentro de la fuente SqlClient, usan un código especial para manejar casos como la configuración de la precisión de un tipo Decimal a partir de la precisión SQL correspondiente.

Tenga en cuenta que si la única razón por la que necesita el tipo de .NET es poder almacenar datos en un parámetro de proceso almacenado, puede intentar simplemente usar ToString() en todos sus valores .NET, rellenando una cadena en la propiedad Value de SqlParameter, y ver si el framework hará la conversión/el análisis por usted. Por ejemplo, para un parámetro XML o Date, puede ser que pueda salirse con la suya enviando una cadena.

Además, en lugar de utilizar la reflexión para encontrar un método Parse() en cada tipo, dado que hay una lista de tipos conocida (y pequeña), puede obtener un mejor rendimiento utilizando un código de análisis fuertemente tipado para cada una, como código a continuación. (Tenga en cuenta que varios tipos (por ejemplo, SqlDbType).UDT) no tienen necesariamente un analizador obvia method-- tendrá que averiguar cómo desea manejar los.)

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

El código para utilizar anterior es bastante fácil, por ejemplo, :

 string valueToSet = "1234"; 
     SqlParameter p = new SqlParameter(); 
     p.SqlDbType = System.Data.SqlDbType.Int; 
     p.Value = TypeMapper[p.SqlDbType](valueToSet); 
3

Creo que te falta un paso aquí. Lo primero que debe hacer es consultar en la base de datos la definición del proceso almacenado mediante una llamada de selección y una combinación interna a la tabla sys objects o mediante el uso de un contenedor de administración. Luego puede "inferir" los tipos de parámetros en función de la información devuelta.

Aquí es un MSO lin k para empezar

Y un ejemplo de how to query the database structure directamente

Si ejecuta el SQL desde el segundo ejemplo, contra la base de datos se verá exactamente lo que pasa:

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 
+0

No estoy seguro de cómo se hace (estoy ampliando el código de otra persona), pero interar a través de SqlCommand.Parameters me muestra todos los parámetros necesarios. ¿Cómo puedo inferir los tipos? – nothrow

+0

No se puede inferir realmente el tipo. Tienes que saber el tipo de alguna manera. Por lo general, el tipo está codificado pero busca una forma automática y la única manera de hacerlo, para "conocer" el tipo, es consultar el archivo db. –

+0

Creo que podría necesitar una forma de mapear desde el tipo SQL al tipo .NET correspondiente. p.ej. Varchar => String, etc. – Knobloch

3

Puede no necesariamente implícita y precisión extraer la correcta .NET CTS ("subyacente") tipo, ya que podría cambiar en función del valor en el parámetro - .DbType y .SqlDbType del SqlParameter son mutables y explícitamente configurable por el programador (o el motor de generación de código) En el caso de un parámetro de salida, el tipo .DbType/.SqlDbType puede estar equivocado incluso después de haber estado correcto durante un tiempo, por ejemplo, si el valor debajo que vuelve repentinamente es diferente de lo esperado en términos .NET. Los valores son manejados por el almacén de datos y .NET SqlParameter lo hace lo mejor que puede con sus tipos explícitos. El valor de los datos de SqlParameter debe considerarse débilmente tipeado en términos de .NET (evidenciado por el valor de retorno System.Object de la propiedad parm.Value).

Su mejor apuesta es

  1. Utilice uno de los métodos de mapeo descritos por otros carteles - por supuesto que tiene su propia suposición implícita de que el tipo de parámetro de SQL siempre será correcta para los datos en ella.
  2. posiblemente pruebe el valor que vuelve del parámetro de salida y suponga que los valores sucesivos son del mismo tipo. Por supuesto, eso depende de la base de datos.
  3. Busque otra estrategia en lugar de confiar en el espacio de nombres Microsoft Sql: puede que sea mucho más feliz en el futuro.

Probando el valor de un tipo .NET CTS se parecería a System.Type t = paramInstance.Value.GetType(); Null causará una excepción. Aún necesitaría lanzarlo apropiadamente con un interruptor o si/else, a menos que saque algunas técnicas de reflexión elegantes.

1

Si puede resolver el SqlType correcto, Reflection le proporcionará la conversión explícita a un tipo .NET. El valor de retorno sería el System.Type subyacente. El almacenamiento en caché del resultado debe compensar la perforación en la primera búsqueda.

1

Echa un vistazo a lo que hacen en linq to sql t4, parece que funciona bien.

Puede encontrar lo que necesita mirando el código.

4

Parece que nadie más quiere decírtelo, pero lo que estás haciendo probablemente no sea la mejor manera de hacerlo.

object correctParam = param.GetNETType().GetMethod("Parse", 
    new Type[] { typeof(string) }).Invoke(value.ToString()); 
param.Value = correctParam; 

¿Estás diciendo que le den un valor de cadena, que usted conoce tiene que ser asignado a un parámetro, y que desea rellenar ese valor en Hay alguna manera de que pueda caber?

Considera por qué estás haciendo esto. Usted está haciendo la suposición de que el siguiente código es correcto:

param.Value = NetType.Parse(value.toString()) 

No hay ninguna razón clara por qué esto es mejor que:

param.Value = value; 

Pero ya que usted quiere hacerlo, parece seguro asumir que ha intentado esto y ha descubierto que su problema real es que value no es el tipo correcto para el parámetro.Por lo tanto, desea una solución mágica que pueda ejecutar, que siempre se asegurará de que value sea del tipo correcto. Lo que realmente quiere es probable:

SetParam(param, value);

Cuando esta función se mete en el valor del parámetro. Esto realmente facilita las cosas si value no es simplemente del tipo object como dices, sino que tiene un tipo real (como int o string). Esto se debe a que puede usar sobrecarga de método como SetParam(SqlParam param, int value) o genéricos para inferir el tipo de valor SetParam<T>(SqlParam param, T value).

Así que sabemos la función que desea, lo que no sabemos es por qué. En la mayoría de los escenarios razonables, tiene una idea de los tipos de valores, y también tiene una idea del tipo de parámetro. Está solicitando una forma de meter un valor que no concuerde con un parámetro en un parámetro que no comprenda.

Hay dos razones principales que se me ocurre para esta solicitud:

  1. Usted en realidad sabe que los tipos son compatibles, y está buscando una manera general de hacer esto para evitar escribir una gran cantidad de código. Entonces sabe que está intentando asignar un long a un parámetro que es un SqlInt, y se basa en las conversiones de cadenas para superar los problemas de seguridad del tipo.

  2. Realmente no entiende el código que está utilizando y está tratando de aplicar un parche para que funcione.

Es muy importante ser honesto consigo mismo acerca de este caso que se encuentre. Si se encuentra en el primer caso, entonces se puede escribir un método como SetParam que he descrito anteriormente con bastante facilidad. Tendrá que escribir una instrucción switch (o como la mejor respuesta anterior, una búsqueda en el diccionario). Tendrás que perder precisión (lanzar un largo a un int no funciona para números grandes, pero tampoco lo hará tu Parse) pero funcionará.

Si está en el segundo caso, deténgase un minuto. Reconozca que se está preparando para más errores en el futuro (porque la conversión hacia y desde la cadena no resolverá los problemas que tiene de no entender los tipos). Sabes que necesitas ayuda, por eso estás en Stack Overflow y ofreces una recompensa por ayuda, y estás lidiando con una base de código que no comprendes. Puedo deducir ahora de su pregunta que va a cavar un agujero más profundo de lo que cree si esta es su situación, porque ya ha rechazado la mejor respuesta (para hacer una declaración de cambio basada en el tipo de parámetro) sin una razón importante .

Por lo tanto, si está en el segundo caso, lo que más le ayudará no es una respuesta de Stack Overflow, a menos que esté dispuesto a describir su problema real de forma más completa. Lo que lo ayudará a comprender de dónde provienen los valores (¿es UI? ¿Es un subsistema diferente, qué reglas siguen? ¿Existe alguna razón por la que los tipos no coincidan?) Y hacia dónde se dirigen (¿cuál es el definición del procedimiento almacenado que está llamando? ¿Cuáles son los tipos de parámetros definidos como?). Me imagino que probablemente ni siquiera necesite entrar en SQL para encontrar esto, ya que quien le dio el SqlParam probablemente ya lo haya definido correctamente para usted. Si lo definió, de hecho necesita ir al SQL para resolverlo, de inmediato.

Cuestiones relacionadas