web-dev-qa-db-fra.com

Type de système .NET vers SqlDbType

Je recherchais une conversion intelligente entre .Net System.Type et SqlDbType. Ce que j'ai trouvé était l'idée suivante:

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 c’est une odeur de code. C’est pourquoi j’ai écrit la fonction suivante, naïve, pas intelligente, mais utile, basée sur 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)];
     }

Quelqu'un a-t-il une idée de la façon d'obtenir le même résultat de manière plus propre, plus efficace et plus agréable?

13
Simone Salvo

Votre approche est un bon début, mais il ne faut renseigner ce dictionnaire que une fois, comme le dit Ian dans un commentaire.

Il y a un Gist ici basé sur la même idée, bien qu'il ne convertisse pas entre les mêmes types de types: https://Gist.github.com/abrahamjp/858392

Caveat

J'ai un exemple de travail ci-dessous, mais vous devez savoir que cette approche pose quelques problèmes. Par exemple:

  • Pour un string, comment choisir le bon 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, devriez-vous choisir Decimal, Float, Money, SmallMoney ou Real?
  • Pour un DateTime, avez-vous besoin de DateTime2, DateTimeOffset, DateTime ou SmallDateTime?
  • Utilisez-vous des types Nullable, comme int?? Ceux-ci devraient probablement donner la même SqlDbType que le type sous-jacent.

De plus, le simple fait de fournir une Type ne vous dit rien des autres contraintes, telles que la taille et la précision du champ. Prendre la bonne décision concerne également la manière dont les données sont utilisées dans votre application et leur stockage dans la base de données.

La meilleure chose à faire est vraiment 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'utiliseriez:

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

Il semble que ce type de table de recherche soit déjà disponible, mais pas dans System.Data (ou .Object ou .Type), mais plutôt dans System.Web.

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

Alors 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, les types SqlDbType et DbType sont liés . Par conséquent, la définition de DbType modifie le SqlDbType en un SqlDbType.

Donc, cela devrait théoriquement fonctionner;)

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

Edit: Je pensais à et cela fonctionne pour les types System.Data.SqlTypes. Je vais le laisser ici juste au cas où cela pourrait aider quelqu'un à l'avenir.

Je fais quelque chose comme ça:

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 a une propriété .Value qui est le type CLR sous-jacent réel, j’utilise la réflexion pour l’obtenir. Il est dommage que SqlDbType n'ait pas d'interface qui contiendrait cette propriété .Value et une réflexion ne serait pas nécessaire.
Ce n'est pas parfait, mais vous n'avez pas besoin de créer, de maintenir ou de renseigner manuellement un dictionnaire. Vous pouvez simplement rechercher un type dans un dict existant, et s'il n'existe pas, utilisez la méthode supérieure pour ajouter le mappage automatiquement .
.__ prend également en charge tous les nouveaux types que SQL Server pourrait recevoir à l’avenir.

0
Mladen Prajdic