2016-03-02 20 views
8

Stavo cercando una conversione intelligente tra .Net System.Type e SqlDbType. Quello che ho trovato è stata la seguente idea:Tipo di sistema .NET a SqlDbType

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

Il codice di cui sopra non è veramente bello ed è un odore di codice, che è il motivo per cui ho scritto la seguente funzione, ingenuo, non è intelligente, ma utile, sulla base di 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)]; 
    } 

Qualcuno ha idea di come ottenere lo stesso risultato in modo più pulito, migliore e più bello?

+2

Fare conversione dizionario è OK. Fatto * una volta * in una vita. :) (meno c'è un cambiamento) – Ian

+0

Se la mia risposta l'ha aiutata, contrassegnala come risposta selezionata. :) –

risposta

13

Il tuo approccio è un buon inizio, ma il popolamento di quel dizionario dovrebbe essere fatto solo una volta, come dice Ian in un commento.

C'è un GIST qui che si basa sulla stessa idea, anche se non converte tra gli stessi gruppi di tipi: https://gist.github.com/abrahamjp/858392

avvertimento

ho a working example qui di seguito, ma è necessario essere consapevoli che questo approccio ha alcuni problemi. Per esempio:

  • Per un string, come si fa a scegliere quella corretta tra Char, NChar, VarChar, NVarChar, Text o NText(o anche Xml, forse)?
  • E per i BLOB come byte[], si dovrebbe usare Binary, VarBinary o Image?
  • Per decimal, float e double, si dovrebbe andare per Decimal, Float, Money, SmallMoney o Real?
  • Per un DateTime, è necessario DateTime2, DateTimeOffset, DateTime o SmallDateTime?
  • Stai utilizzando i tipi Nullable, ad esempio int?? Molto probabilmente dovrebbero dare lo stesso SqlDbType come tipo sottostante.

Inoltre, solo fornendo un Type non si dice nulla di altri vincoli, come la dimensione del campo e la precisione. Prendere la decisione giusta riguarda anche il modo in cui i dati vengono utilizzati nell'applicazione e il modo in cui vengono archiviati nel database.

La cosa migliore da fare è fare in modo che un ORM lo faccia per te.

Codice

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

e questo è come lo si dovrebbe utilizzare:

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

Sembra buono! Vorrei solo aggiungere un controllo per vedere se il tipo esiste ('ContainsKey') nel dizionario e se non lanciare un' NotSupportedException' (o un'eccezione personalizzata) con il tuo messaggio dettagliato invece del 'KeyNotFoundException 'predefinito. Questo potrebbe rendere più facile la risoluzione dei problemi in seguito se un tipo non supportato è mai passato. – Igor

+1

Grazie per il suggerimento. Ho modificato la risposta per lanciare una 'ArgumentException', in quanto' NotSupportedException' non è pensata per questo tipo di cose. –

0

Edit: Stavo pensando a questo e funziona per i tipi System.Data.SqlTypes.Lascerò qui solo nel caso in cui aiuti qualcuno in futuro.

mi fare qualcosa di simile:

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

Perché ogni SqlDbType ha una proprietà .Value che è il sottostante tipo effettivo CLR Io uso riflessione per ottenerlo. È troppo male che SqlDbType non abbia un'interfaccia che possa contenere questo. La proprietà e la riflessione di valore non sarebbero necessarie.
Non è perfetto ma non è necessario creare manualmente, mantenere o popolare un dizionario. Puoi semplicemente cercare un tipo in un dict esistente e, se non esiste, utilizzare il metodo superiore per aggiungere automaticamente il mapping. Generato automaticamente.
Si occupa anche di tutti i nuovi tipi che SQL Server potrebbe ricevere in futuro.

+0

"Modificato: non so dove sia andato il commento a cui stavo rispondendo." ah, hai ragione, per l'altra direzione non ho una risposta migliore di un dizionario pre-compilato. Sebbene di solito il caso d'uso sia da tipo sql a tipo clr perché un tipo sql può essere associato a più tipi di clr. –

+0

Sembra che [SqlDbType] (https://msdn.microsoft.com/en-us/library/system.data.sqldbtype (v = vs.110) .aspx) sia un'enumerazione, quindi non sono sicuro di come ciò contenga ulteriori informazioni su un tipo CLR. Anche per la creazione di query non funzionerebbe, solo per tradurre i risultati da una query al tipo CLR corretto. – Igor

+0

Mi spiace, ho cancellato subito il mio commento perché volevo ripensare un po '. Il mio commento iniziale era * "Non va nella direzione opposta?" *. Ora sono più con @Igor, poiché il desiderato 'SqlDbType' è un'enumerazione. –

0

Sembra che questo tipo di tabella di ricerca è già disponibile, anche se non in System.Data (o .Object o .Type), ma piuttosto in System.Web.

Progetto -> Aggiungi riferimento -> System.Web -> OK

Poi https://msdn.microsoft.com/en-us/library/system.data.sqldbtype(v=vs.110).aspx dice anche

Quando si impostano parametri di comando, lo SqlDbType e DbType sono collegati. Pertanto, l'impostazione di DbType cambia SqlDbType in un SqlDbType di supporto .

Quindi, questo dovrebbe teoricamente funzionare;)

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