Obtenir le type de données à partir des valeurs transmises sous forme de chaîne

J’écris un cadre qui se connecte à de nombreux types de sources de données et renvoie des valeurs provenant de ces sources. Les plus faciles sont SQL, Access et Oracle. Les plus difficiles sont Sharepoint, CSV.

Si je retourne des valeurs à partir de sources textuelles, j’aimerais déterminer le type de données des données.

Comme un fichier CSV contient uniquement du texte et qu’il n’y a pas de métadonnées à interroger, il me faudrait parsingr les données d’une manière ou d’une autre pour déterminer le type de données.

Exemple:

Les listes “true”, “true”, “false”, “false” seraient booléennes
Liste de “1”, “0”, “1”, “0” serait booléen
La liste de “1”, “4”, “-10”, “500” serait un entier
Liste de “15.2”, “2015.5896”, “1.0245”, “500” serait double
Liste de “2001/01/01”, “2010/05/29 12:00”, “1989/12/25 10:34:21” serait datetime

Il est basé sur https://stackoverflow.com/questions/606365/c-doubt-finding-the-datatype/606381#606381

object ParseSsortingng(ssortingng str) { Int32 intValue; Int64 bigintValue; double doubleValue; bool boolValue; DateTime dateValue; // Place checks higher in if-else statement to give higher priority to type. if (Int32.TryParse(str, out intValue)) return intValue; else if (Int64.TryParse(str, out bigintValue)) return bigintValue; else if (double.TryParse(str, out doubleValue)) return doubleValue; else if (bool.TryParse(str, out boolValue)) return boolValue; else if (DateTime.TryParse(str, out dateValue)) return dateValue; else return str; } 

Edit: Je n’ai besoin que de répondre aux besoins suivants:

 BIT DATETIME INT NVARCHAR(255) NVARCHAR(MAX) BIGINT DECIMAL(36, 17) 

Voyez-vous une amélioration possible de la priorité?

J’ai mis au point la solution suivante qui fonctionne:

 enum dataType { System_Boolean = 0, System_Int32 = 1, System_Int64 = 2, System_Double = 3, System_DateTime = 4, System_Ssortingng = 5 } private dataType ParseSsortingng(ssortingng str) { bool boolValue; Int32 intValue; Int64 bigintValue; double doubleValue; DateTime dateValue; // Place checks higher in if-else statement to give higher priority to type. if (bool.TryParse(str, out boolValue)) return dataType.System_Boolean; else if (Int32.TryParse(str, out intValue)) return dataType.System_Int32; else if (Int64.TryParse(str, out bigintValue)) return dataType.System_Int64; else if (double.TryParse(str, out doubleValue)) return dataType.System_Double; else if (DateTime.TryParse(str, out dateValue)) return dataType.System_DateTime; else return dataType.System_Ssortingng; } ///  /// Gets the datatype for the Datacolumn column ///  /// Datacolumn to get datatype of /// DataTable to get datatype from /// ref value to return size for ssortingng type ///  public Type GetColumnType(DataColumn column, DataTable dt, ref int colSize) { Type T; DataView dv = new DataView(dt); //get smallest and largest values ssortingng colName = column.ColumnName; dv.RowFilter = "[" + colName + "] = MIN([" + colName + "])"; DataTable dtRange = dv.ToTable(); ssortingng strMinValue = dtRange.Rows[0][column.ColumnName].ToSsortingng(); int minValueLevel = (int)ParseSsortingng(strMinValue); dv.RowFilter = "[" + colName + "] = MAX([" + colName + "])"; dtRange = dv.ToTable(); ssortingng strMaxValue = dtRange.Rows[0][column.ColumnName].ToSsortingng(); int maxValueLevel = (int)ParseSsortingng(strMaxValue); colSize = strMaxValue.Length; //get max typelevel of first n to 50 rows int sampleSize = Math.Max(dt.Rows.Count, 50); int maxLevel = Math.Max(minValueLevel, maxValueLevel); for (int i = 0; i < sampleSize; i++) { maxLevel = Math.Max((int)ParseString(dt.Rows[i][column].ToString()), maxLevel); } string enumCheck = ((dataType)maxLevel).ToString(); T = Type.GetType(enumCheck.Replace('_', '.')); //if typelevel = int32 check for bit only data & cast to bool if (maxLevel == 1 && Convert.ToInt32(strMinValue) == 0 && Convert.ToInt32(strMaxValue) == 1) { T = Type.GetType("System.Boolean"); } if (maxLevel != 5) colSize = -1; return T; } 

Puisque Dimi a mis une prime et a besoin d’une solution plus “moderne”, je vais essayer de vous en fournir une. Premièrement, de quoi avons-nous besoin d’une classe raisonnable qui convertit les chaînes en différentes choses?

Comportement raisonnable avec les types de base.

Respectez les informations sur la culture, en particulier lors de la conversion des chiffres et des dates.

Possibilité d’étendre la logique avec des convertisseurs personnalisés si nécessaire.

En prime, évitez les longues chaînes “if” car elles sont assez sujettes aux erreurs.

 public class SsortingngConverter { // delegate for TryParse(ssortingng, out T) public delegate bool TypedConvertDelegate(ssortingng value, out T result); // delegate for TryParse(ssortingng, out object) private delegate bool UntypedConvertDelegate(ssortingng value, out object result); private readonly List _converters = new List(); // default converter, lazyly initialized private static readonly Lazy _default = new Lazy(CreateDefault, true); public static SsortingngConverter Default => _default.Value; private static SsortingngConverter CreateDefault() { var d = new SsortingngConverter(); // add reasonable default converters for common .NET types. Don't forget to take culture into account, that's // important when parsing numbers\dates. d.AddConverter(bool.TryParse); d.AddConverter((ssortingng value, out byte result) => byte.TryParse(value, NumberStyles.Integer, d.Culture, out result)); d.AddConverter((ssortingng value, out short result) => short.TryParse(value, NumberStyles.Integer, d.Culture, out result)); d.AddConverter((ssortingng value, out int result) => int.TryParse(value, NumberStyles.Integer, d.Culture, out result)); d.AddConverter((ssortingng value, out long result) => long.TryParse(value, NumberStyles.Integer, d.Culture, out result)); d.AddConverter((ssortingng value, out float result) => float.TryParse(value, NumberStyles.Number, d.Culture, out result)); d.AddConverter((ssortingng value, out double result) => double.TryParse(value, NumberStyles.Number, d.Culture, out result)); d.AddConverter((ssortingng value, out DateTime result) => DateTime.TryParse(value, d.Culture, DateTimeStyles.None, out result)); return d; } // public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture; public void AddConverter(Predicate match, Func converter) { // create converter from match predicate and convert function _converters.Add((ssortingng value, out object result) => { if (match(value)) { result = converter(value); return true; } result = null; return false; }); } public void AddConverter(Regex match, Func converter) { // create converter from match regex and convert function _converters.Add((ssortingng value, out object result) => { if (match.IsMatch(value)) { result = converter(value); return true; } result = null; return false; }); } public void AddConverter(TypedConvertDelegate constructor) { // create converter from typed TryParse(ssortingng, out T) function _converters.Add(FromTryPattern(constructor)); } public bool TryConvert(ssortingng value, out object result) { if (this != Default) { // if this is not a default converter - first try convert with default if (Default.TryConvert(value, out result)) return true; } // then use local converters. Any will return after the first match object tmp = null; bool anyMatch = _converters.Any(c => c(value, out tmp)); result = tmp; return anyMatch; } private static UntypedConvertDelegate FromTryPattern(TypedConvertDelegate inner) { return (ssortingng value, out object result) => { T tmp; if (inner.Invoke(value, out tmp)) { result = tmp; return true; } else { result = null; return false; } }; } } 

Utilisez comme ceci:

 static void Main(ssortingng[] args) { // set culture to invariant SsortingngConverter.Default.Culture = CultureInfo.InvariantCulture; // add custom converter to default, it will match ssortingngs starting with CUSTOM: and return MyCustomClass SsortingngConverter.Default.AddConverter(c => c.StartsWith("CUSTOM:"), c => new MyCustomClass(c)); var items = new[] {"1", "4343434343", "3.33", "true", "false", "2014-10-10 22:00:00", "CUSTOM: something"}; foreach (var item in items) { object result; if (SsortingngConverter.Default.TryConvert(item, out result)) { Console.WriteLine(result); } } // create new non-default converter var localConverter = new SsortingngConverter(); // add custom converter to parse json which matches schema for MySecondCustomClass localConverter.AddConverter((ssortingng value, out MySecondCustomClass result) => TryParseJson(value, @"{'value': {'type': 'ssortingng'}}", out result)); { object result; // check if that works if (localConverter.TryConvert("{value: \"Some value\"}", out result)) { Console.WriteLine(((MySecondCustomClass) result).Value); } } Console.ReadKey(); } static bool TryParseJson(ssortingng json, ssortingng rawSchema, out T result) where T : new() { // we are using Newtonsoft.Json here var parsedSchema = JsonSchema.Parse(rawSchema); JObject jObject = JObject.Parse(json); if (jObject.IsValid(parsedSchema)) { result = JsonConvert.DeserializeObject(json); return true; } else { result = default(T); return false; } } class MyCustomClass { public MyCustomClass(ssortingng value) { this.Value = value; } public ssortingng Value { get; private set; } } public class MySecondCustomClass { public ssortingng Value { get; set; } } 
  List types = new List(new Type[] { typeof(Boolean) , typeof(int) , typeof(double) , typeof(DateTime) }); ssortingng t = "true"; object retu; foreach (Type type in types) { TypeConverter tc = TypeDescriptor.GetConverter(type); if (tc != null) { try { object obj = tc.ConvertFromSsortingng(t); // your return value; } catch (Exception) { continue; } } } 

Serait-il plus facile de le stocker dans un type de données générique avec .ToInt16 (), .ToInt32 (), .ToBool (), etc.? Si vous écrivez une application qui attend un int et qu’elle devient booléenne, elle échouera. Il serait donc préférable de laisser le programmeur explicite convertir au type de données attendu.

Le problème avec votre approche est que vous ne savez pas si une ligne contenant 0 comme premier élément contiendra -100000 comme numéro d’article 100. Cela signifie que vous ne pourrez pas effectuer de conversion réussie tant que toutes les lignes n’auront pas été vérifiées. Types de données. Opération très chère!

Si possible, j’utiliserais des expressions régulières précompilées et / ou une logique personnalisée pour traiter les données. Par exemple, itérer toutes les lignes pour trouver le nombre le plus élevé / le plus bas, l’occurrence d’une chaîne, etc.

Commencer par les types les plus étroits et tendre vers le plus large peut ne pas être la meilleure approche. Si je savais quelque chose à propos des données, je commencerais par le type le plus fréquent et travaillerais vers le moins. Si je ne le savais pas, je pourrais ou non faire des recherches pour avoir une idée de ce que cela pourrait être statistiquement, si possible. Sinon, je devrais faire de mon mieux. Pourquoi tester tôt le bit ou la date-heure si vous vous attendez à ce qu’il se produise tous les 10 000 enregistrements?