Come recuperare il tipo .NET di un determinato parametro di StoredProcedure in SQL?

Sto creando un wrapper ‘generico’ sopra le procedure SQL e posso risolvere tutti i nomi dei parametri richiesti e sqltypes, ma c’è un modo per ottenere il tipo .NET ‘sottostante’?

Il mio objective è fare qualcosa come:

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

Dove GetNETType è la cosa di cui ho bisogno. So che può essere scritto come switch all’interno di param.SqlDbType, ma questo è il modo più breve, e il codice commentato più corto significa meno mainteance 🙂

Sfortunatamente, per quanto ne so, questa mapping non è esposta nel codice all’interno di .NET Framework. Ho esaminato la fonte di riferimento di .NET Framework in precedenza e ho scoperto che all’interno del codice .NET ci sono un sacco di lunghe istruzioni switch per tipo, proprio come quelle che stai cercando di evitare, ma nessuno di loro sembra essere esposti esternamente.

Se vuoi solo mappare da SqlTypes al tipo .NET più simile, penso che la soluzione migliore sia semplicemente trasformare la tabella di mapping nei documenti MSDN in codice. Si noti che la tabella su MSDN ha (almeno) due errori: n. 1: non esiste un tipo .NET chiamato “DateTime2” (ho usato DateTime) e non esiste nemmeno un tipo chiamato “Xml” (ho usato SqlXml).

Ad ogni modo, ecco la mapping che sto usando– usando un dizionario al posto di uno switch per facilità di accesso senza un metodo separato.

 public static Dictionary TypeMap = new Dictionary { { 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) }, }; 

Si noti che una cosa di cui bisogna fare attenzione è la dimensione / precisione – alcuni tipi di SQL (es. varchar ) hanno limiti di dimensione, mentre i tipi .NET (es. string ) non lo fanno. Quindi essere in grado di conoscere il tipo .NET più probabile non è davvero sufficiente … se lo si utilizza, ad esempio, per guidare le regole di convalida, è necessario essere in grado di impedire agli utenti di entrare non validi (ad es. valori sapendo di più sul parametro, come la precisione. Si noti che, se si guarda all’interno dell’origine SqlClient, viene utilizzato un codice speciale per gestire casi come l’impostazione della precisione di un tipo Decimal dalla precisione SQL corrispondente.

Nota che se l’unica ragione per cui hai bisogno del tipo .NET è essere in grado di inserire dati in un parametro proc memorizzato, potresti provare semplicemente a utilizzare ToString () su tutti i tuoi valori .NET, inserendo una stringa nella proprietà Value di SqlParameter e vedere se il framework eseguirà la conversione / analisi per te. Ad esempio, per un parametro XML o Date potresti invece riuscire a mandare una stringa.

Inoltre, anziché utilizzare il reflection per trovare un metodo Parse () su ciascun tipo, poiché esiste un elenco noto (e piccolo) di tipi, è ansible ottenere prestazioni migliori utilizzando un codice di analisi fortemente tipizzato per ciascuno, come il seguente codice. (Si noti che diversi tipi (es. SqlDbType.Udt) non hanno necessariamente un metodo parser ovvio, dovrete capire come volete gestirli).

 public static Dictionary> TypeMapper = new Dictionary> { { 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 }, }; 

Il codice da utilizzare sopra è piuttosto semplice, ad esempio:

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

Nessun altro sembra voler dirtelo, ma quello che stai facendo probabilmente non è il modo migliore per farlo.

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

Stai dicendo che ti viene assegnato un valore stringa, che sai deve essere assegnato a un parametro, e vuoi inserire quel valore in ogni modo che può adattarsi?

Per favore, considera perché stai facendo questo. Stai supponendo che il codice seguente sia corretto:

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

Non c’è una ragione chiara per cui è meglio di:

 param.Value = value; 

Ma dal momento che si desidera farlo, sembra sicuro assumere che si è provato questo e ha scoperto che il tuo vero problema è che il value non è il tipo giusto per il parametro. Quindi vuoi una correzione magica che puoi eseguire, che si assicurerà sempre che quel value sia il tipo giusto. Quello che vuoi veramente è probabile:

SetParam(param, value);

Dove questa funzione inserisce il valore nel parametro. Questo rende le cose un po ‘più semplici se value non è semplicemente di tipo object come dici tu, ma ha un tipo reale (come int o string ). Questo perché è ansible utilizzare l’overloading del metodo come SetParam(SqlParam param, int value) o generici per inferire il tipo di valore SetParam(SqlParam param, T value) .

Quindi conosciamo la funzione che vuoi, ciò che non sappiamo è perché. Nella maggior parte degli scenari ragionevoli hai un’idea dei tipi di valori e hai anche un’idea del tipo di parametro. Stai chiedendo un modo per stipare un valore che non corrisponde a un parametro in un parametro che non capisci.

Ci sono due ragioni principali per cui posso pensare a questa richiesta:

  1. In realtà, sai che i tipi sono compatibili e stai cercando un modo generale di farlo per evitare di scrivere molto codice. Quindi sai che stai provando ad assegnare un long ad un parametro che è un SqlInt e ti affidi alle conversioni di stringhe per superare i problemi di sicurezza del tipo.

  2. Non si capisce il codice che si sta utilizzando e si sta tentando di applicare una correzione per ottenere un risultato positivo.

È davvero importante essere onesti con te stesso sul caso in cui ti trovi. Se sei nel primo caso, puoi scrivere un metodo come SetParam che ho descritto sopra abbastanza facilmente. Dovrai scrivere una dichiarazione switch (o come la migliore risposta sopra, una ricerca del dizionario). Dovrai perdere la precisione (lanciare un long ad un int non funziona per grandi numeri, ma neanche il tuo Parse) ma funzionerà.

Se sei nel secondo caso, fermati per un minuto. Riconosci che ti stai preparando per altri bug in futuro (perché la conversione da e verso la stringa non risolverà i problemi che hai di non capire i tipi). Sai che hai bisogno di aiuto, ed è per questo che sei su Stack Overflow e offri una taglia per aiuto, e hai a che fare con un codebase che non capisci. Posso dire in questo momento dalla tua domanda che ti stai scavando un buco più profondo di quello che ti rendi conto se questa è la tua situazione, perché hai già rifiutato la risposta migliore (per fare un’istruzione switch in base al tipo di parametro) senza una forte motivazione .

Quindi, se sei nel secondo caso, la cosa che ti aiuterà di più non è una risposta da Stack Overflow, a meno che tu non sia disposto a descrivere il tuo vero problema in modo più completo. Ciò che ti aiuterà è capire da dove provengono i valori (è l’interfaccia utente? È un sottosistema diverso, quali regole seguono? C’è un motivo per cui i tipi non corrispondono?) E dove stanno andando (qual è il definizione della stored procedure che stai chiamando? Quali sono i tipi di parametri definiti come?). Immagino che probabilmente non hai nemmeno bisogno di andare in SQL per trovarlo, visto che chiunque ti ha dato lo SqlParam probabilmente l’ha già definito correttamente per te. Se lo hai definito, devi davvero andare all’SQL per capirlo, immediatamente.

Penso che ti manchi un passaggio qui. La prima cosa che devi fare è interrogare il database per la definizione del processo memorizzato tramite una chiamata selezionata e un join interno alla tabella degli oggetti sys o utilizzando un wrapper di gestione. Quindi è ansible “inferire” i tipi di parametri in base alle informazioni restituite.

Ecco un MSO lin k per iniziare

E un esempio di come interrogare direttamente la struttura del database

Se esegui SQL dal secondo esempio sul tuo database, vedrai esattamente cosa succede:

 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 

Non è ansible estrarre implicitamente e accuratamente il tipo corretto di .NET CTS (“sottostante”) in quanto potrebbe cambiare in base al valore nel parametro: SqlParameter’s .DbType e .SqlDbType sono mutabili e configurabili in modo esplicito dal programmatore (o dal codice- motore di generazione) Nel caso di un parametro di output, il .DbType / .SqlDbType può essere errato anche dopo essere stato corretto per un po ‘, ad esempio se il valore sottostante torna improvvisamente diverso da quello previsto in termini .NET. I valori sono gestiti dall’archivio dati e .NET SqlParameter gestisce il meglio ansible con i suoi tipi espliciti. Il valore dei dati di SqlParameter deve essere considerato debolmente tipizzato in termini .NET (evidenziato dal valore di ritorno System.Object della proprietà parm.Value).

La tua migliore scommessa è

  1. Utilizzare uno dei metodi di mapping delineato da altri poster – ovviamente che ha la propria ipotesi implicita che il tipo di parametro SQL sarà sempre corretto per i dati in esso contenuti.
  2. eventualmente testare il valore che ritorna dal parametro di uscita e assumere valori successivi dello stesso tipo. Certo che è davvero all’altezza del database.
  3. Trova un’altra strategia invece di affidarti allo spazio dei nomi Microsoft Sql: potresti essere più felice in futuro.

Testare il valore per un tipo .NET CTS sarebbe simile a System.Type t = paramInstance.Value.GetType(); Null causerà un’eccezione. Avresti ancora bisogno di lanciarlo in modo appropriato usando un interruttore o se / else, a meno che non tiri fuori alcune fantasiose tecniche di riflessione.

Se è ansible risolvere il tipo SqlType corretto, Reflection otterrà il cast esplicito su un tipo .NET. Il valore restituito sarebbe il sottostante System.Type. La memorizzazione nella cache del risultato dovrebbe compensare il perfetto alla prima ricerca.

Dai un’occhiata a quello che fanno in linq to sql t4 , sembra funzionare bene.

Potresti essere in grado di scoprire di cosa hai bisogno guardando il codice.