2013-06-10 1 views
2

J'utilise le code suivant pour lire les données de différents fichiers Excel:lecture des fichiers Excel d'une manière indépendante locale

// IMEX=1 - to force strings on mixed data 
    // HDR=NO - to process all the available data 
    // Locale 1033 is en-US. This was my first attempt to force en-US locale. 
    string connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Locale Identifier=1033;Extended Properties=\"{1};READONLY=TRUE;HDR=NO;IMEX=1;\""; 

    // source type according to the 
    // http://www.microsoft.com/en-us/download/details.aspx?id=13255 

    // try determining from extension 
    bool isOldFormat = 
      Path.GetExtension(sourceExcel).Equals(".xls", StringComparison.OrdinalIgnoreCase); 

    bool isBinary = 
      Path.GetExtension(sourceExcel).Equals(".xlsb", StringComparison.OrdinalIgnoreCase); 

    string sourceType = isOldFormat ? "Excel 8.0" : "Excel 12.0"; 

    if (!isOldFormat) 
     sourceType += " Xml"; // for some reason the new binary xlsb files also need Xml 

    connectionString = string.Format(connectionString, sourceExcel, sourceType); 

    // this was my second attempt to force Excel to use US culture 
    var oldCulture = Thread.CurrentThread.CurrentCulture; 
    Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US"); 

    var dt = new DataTable(); 
    try 
    { 
     using (var con = new OleDbConnection(connectionString)) 
     { 
      con.Open(); 

      // get all the available sheets 
      using (DataTable dataSet = con.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null)) 
      { 
       // this was my third attempt to force Excel to use US culture 
       dataSet.Locale = CultureInfo.CreateSpecificCulture("en-US"); 
       // get the sheet name in the file (will throw if out of range) 
       string workSheetName = dataSet.Rows[worksheetIndex]["TABLE_NAME"].ToString();//.Trim(new[] { '$' }).Replace("'", ""); 

       string sql = String.Format("select * from [{0}]", workSheetName); 

       var da = new OleDbDataAdapter(sql, con); 
       // this was my fourth attempt to force Excel to use US culture 
       dt.Locale = CultureInfo.CreateSpecificCulture("en-US"); 
       da.Fill(dt); 
      } 

      con.Close(); 
     } 

Comme vous le voyez, j'étais assez désespérée, en essayant de forcer Excel à utiliser en-US paramètres régionaux compatibles lors de l'importation de données. J'ai besoin de ceci parce que mon code pourrait être exécuté sur des serveurs avec divers paramètres régionaux, mais les données ont besoin d'un traitement supplémentaire qui suppose que les données entrantes sont des paramètres régionaux en-US/neutre. J'ai essayé aussi CultureInfo.InvariantCulture au lieu de CultureInfo.CreateSpecificCulture("en-US").

Peu importe comment j'essaie, lorsque les paramètres régionaux du serveur est défini sur un autre lieu qui utilise . comme séparateur de milliers et , comme séparateur décimal, je reçois des résultats erronés dans mon dt DataTable.

Pour comparer le résultat pour une valeur monétaire - 200000,00 £ :

Lorsque les paramètres régionaux du serveur correspondent aux paramètres régionaux des États-Unis, je reçois "-£200,000.00"

Lorsque les paramètres régionaux du serveur correspondent aux paramètres régionaux letton, je reçois "-£200 000,00"

Je ne peux même pas post-traiter les données en utilisant les séparateurs numériques actuels de Thread.CurrentThread.CurrentCulture, car OleDb semble l'ignorer complètement.

D'où OleDb tire-t-il sa culture actuelle? Dois-je dire au fournisseur OleDbConnection ou Microsoft.ACE.OLEDB.12.0 que j'ai besoin des données formatées selon la culture en-US ou Invariant?

Répondre

6

Après beaucoup d'essais et erreurs et après avoir lu cet article pas à jour http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q320744 Je trouve que la version actuelle de OLEDB par défaut semble utiliser la culture de HKEY_CURRENT_USER\Control Panel\International. Malheureusement, je n'ai pas trouvé comment appeler la fonction SetVarConversionLocaleSetting de mon code C# pour forcer OLEDB à utiliser la culture de threads actuelle, donc je suis allé avec le principe - si je ne peux pas ajuster OLEDB pour mon code, alors je vais ajuster mon code à être compatible avec la culture OLEDB. Et après j'en ai fini, je peux convertir toutes les données à la culture invariante.

Mais il y a une partie difficile. Vous ne pouvez pas simplement saisir le séparateur décimal de HKEY_CURRENT_USER\Control Panel\International, car OLEDB ignore les paramètres personnalisés de l'utilisateur pour les formats numériques. OLEDB prend uniquement les valeurs prédéfinies par défaut pour cette culture. Donc, je devais faire ce qui suit:

var oldCulture = Thread.CurrentThread.CurrentCulture; 

using (RegistryKey international = 
     Registry.CurrentUser.OpenSubKey("Control Panel\\International", false)) 
{ 
    string userDefaultCulture = international.GetValue("LocaleName").ToString(); 
    // notice: although the user might have customized his decimal/thousand separators, 
    // still OLEDB ignores these customizations. That is why I create a culture with default settings. 
    cultureToNormalize = new CultureInfo(userDefaultCulture, false); 
} 

// force both OLEDB and current thread cultures to match for the next ToString() etc. conversions in my function 
Thread.CurrentThread.CurrentCulture = cultureToNormalize; 

string decSep = cultureToNormalize.NumberFormat.NumberDecimalSeparator; 
string groupSep = cultureToNormalize.NumberFormat.NumberGroupSeparator; 

et maintenant je peux traiter les données selon les besoins, et aussi je peux appeler ToString() toute sécurité - les cultures de numéros OLEDB et .NET et devises de chaîne de caractères correspondront. Et, pour être un bon garçon, je restaure la culture précédente à la fin de ma fonction.

Si quelqu'un a une meilleure solution, je serai vraiment reconnaissant. Mais pour l'instant je vais le garder tel quel - tous mes tests unitaires sont verts maintenant.

Questions connexes