2016-03-02 2 views
8

Je cherchais une conversion intelligente entre .Net System.Type et SqlDbType. Qu'est-ce que je l'ai trouvé était l'idée suivante:Type de système .NET à 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; 
    } 

Le code ci-dessus n'est pas vraiment agréable et une odeur de code, ce qui est la raison pour laquelle je l'ai écrit ce qui suit, la fonction naïve, pas intelligent, mais utile, sur la base 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)]; 
    } 

Est-ce que quelqu'un a une idée de la façon d'obtenir le même résultat d'une manière plus propre, meilleure et agréable?

+2

Faire la conversion du dictionnaire est OK. Fait * une fois * dans une vie. :) (moins il y a un changement) – Ian

+0

Si ma réponse vous a aidé, veuillez le marquer comme votre réponse sélectionnée. :) –

Répondre

13

Votre approche est un bon début, mais remplir ce dictionnaire devrait seulement être fait une fois, comme Ian dit dans un commentaire.

Il y a un GIST ici qui est basé sur la même idée, même si elle ne convertit pas entre les mêmes ensembles de types: https://gist.github.com/abrahamjp/858392

Caveat

Je a working example ci-dessous, mais vous avez besoin être conscient que cette approche a quelques problèmes. Par exemple:

  • Pour une string, comment choisir la bonne entre Char, NChar, VarChar, NVarChar, Text ou NText(ou même Xml, peut-être)?
  • Et pour les blobs comme byte[], devriez-vous utiliser Binary, VarBinary ou Image?
  • Pour decimal, float et double, si vous optez pour Decimal, Float, Money, SmallMoney ou Real?
  • Pour DateTime, avez-vous besoin de DateTime2, DateTimeOffset, DateTime ou SmallDateTime?
  • Utilisez-vous Nullable types, comme int?? Ceux-ci devraient très probablement donner le même SqlDbType que le type sous-jacent.

De plus, le simple fait de fournir Type ne vous indique rien d'autres contraintes, telles que la taille et la précision du champ. Prendre la bonne décision dépend également de la façon dont les données sont utilisées dans votre application et comment elles sont stockées dans la base de données.

La meilleure chose à faire est de laisser un ORM le faire pour vous.

code

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

Et voici comment vous l'utiliser:

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

Ça a l'air bien! Je voudrais seulement ajouter un contrôle pour voir si le type existe ('ContainsKey') dans le dictionnaire et si pas jeter un' NotSupportedException' (ou exception faite sur commande) avec votre propre message détaillé au lieu du 'KeyNotFoundException' par défaut. Cela pourrait faciliter le dépannage plus tard si un type non supporté est jamais passé. – Igor

+1

Merci pour le conseil. J'ai édité la réponse pour lancer une 'ArgumentException', car' NotSupportedException' n'est pas destiné à ce genre de chose. –

0

Edit: Je pensais et cela fonctionne pour les types de System.Data.SqlTypes.Je vais laisser ici juste au cas où cela aiderait quelqu'un dans le futur.

-je faire quelque chose comme ceci:

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

Parce que chaque SqlDbType possède une propriété .Value qui est le type CLR réelle sous-jacente que j'utilise la réflexion pour l'obtenir. C'est dommage que SqlDbType n'ait pas d'interface qui contiendrait cette propriété .Value et la réflexion ne serait pas nécessaire.
Ce n'est pas parfait, mais vous n'avez pas besoin de créer, maintenir ou remplir manuellement un dictionnaire. Vous pouvez simplement rechercher un type dans un dict existant, et s'il n'existe pas, utilisez la méthode upper pour ajouter le mapping automatiquement. Assez généré automatiquement.
Prend également en charge tous les nouveaux types que SQL Server peut recevoir à l'avenir.

+0

"Modifié: Je ne sais pas où est allé le commentaire auquel je répondais." ah, vous avez raison, pour l'autre direction, je n'ai pas de meilleure réponse qu'un dictionnaire pré-rempli. Bien que généralement le cas d'utilisation soit du type sql au type clr car un type sql peut correspondre à plusieurs types clr. –

+0

On dirait [SqlDbType] (https://msdn.microsoft.com/en-us/library/system.data.sqldbtype (v = vs.110) .aspx) est une énumération donc je ne suis pas sûr de savoir comment cela tient informations sur un type CLR. De même, pour créer des requêtes, cela ne fonctionnerait pas, seulement pour traduire les résultats d'une requête vers le bon type CLR. – Igor

+0

Désolé, j'ai supprimé mon commentaire tout de suite parce que je voulais repenser un peu. Mon commentaire initial était * "Cela ne va-t-il pas dans la direction opposée?" *. Maintenant, je suis plus avec @Igor, car le 'SqlDbType 'désiré est une énumération. –

0

Il semble que cette sorte de table de recherche est déjà disponible, bien que non dans System.Data (ou .Object ou .Type) mais plutôt dans System.Web.

Projet -> Ajouter une référence -> System.Web -> OK

Puis https://msdn.microsoft.com/en-us/library/system.data.sqldbtype(v=vs.110).aspx dit aussi

Lors de la définition des paramètres de commande, le SqlDbType et DbType sont liés. Par conséquent, la définition de DbType modifie le SqlDbType en un SqlDbType de prise en charge.

Donc, cela devrait fonctionner en théorie;)

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