web-dev-qa-db-fra.com

Types de données OleDB et Excel mixte: données manquantes

J'ai une feuille de calcul Excel que je veux lire dans un datatable - tout va bien sauf une colonne particulière de ma feuille Excel. La colonne, 'ProductID', est un mélange de valeurs telles que ########## et n#########.

J'ai essayé de laisser OleDB gérer tout automatiquement par lui-même en le lisant dans un jeu de données/pouvant être daté, mais toutes les valeurs de 'ProductID' comme n###### sont manquantes, ignorées et laissées en blanc. J'ai essayé de créer manuellement mon DataTable en parcourant chaque ligne avec un lecteur de données, mais avec les mêmes résultats.

Voici le code:

// add the column names manually to the datatable as column_1, column_2, ...
for (colnum = 0; colnum < num_columns; colnum ++){
  ds.Tables["products"].Columns.Add("column_" +colnum , System.Type.GetType("System.String")); 
}
while(myDataReader.Read()){
  // loop through each Excel row adding a new respective datarow to my datatable 
  DataRow a_row = ds.Tables["products"].NewRow();
  for (col = 0; col < num_columns; col ++){
    try {  a_row[col] = rdr.GetString(col);  }
    catch {  a_row[col] = rdr.GetValue(col).ToString(); }
  }
  ds.Tables["products"].Rows.Add(a_row);
}

Je ne comprends pas pourquoi cela ne me permet pas de lire des valeurs telles que n######. Comment puis-je faire ceci? 

49
rlb.usa

En utilisant .Net 4.0 et en lisant des fichiers Excel, j’ai eu un problème similaire avec OleDbDataAdapter - c’est-à-dire la lecture d’un type de données mélangé sur une colonne "PartID" dans MS Excel, où la valeur de PartID peut être numérique (par exemple 561) ou texte (par exemple HL4354 ), même si la colonne Excel était au format "Texte". 

D'après ce que je peux dire, ADO.NET choisit le type de données en fonction de la majorité des valeurs de la colonne (avec un lien vers le type de données numérique). C'est-à-dire que si la plupart des PartID de l'échantillon sont numériques, ADO.NET déclarera la colonne numérique. Par conséquent, ADO.Net tentera de convertir chaque cellule en un nombre, ce qui échouera pour les valeurs de PartID "texte" et ne les importera pas. 

Ma solution a été de définir la chaîne de connexion OleDbConnection pour qu'elle utilise Extended Properties=IMEX=1;HDR=NO pour indiquer qu'il s'agit d'une importation et que la ou les tables ne comprendront pas d'en-têtes. Le fichier Excel a une ligne d’en-tête, dans ce cas, dites à ado.net de ne pas l’utiliser. Ensuite, plus loin dans le code, supprimez cette ligne d'en-tête de l'ensemble de données et voilà, vous avez un type de données mélangé pour cette colonne.

string sql = "SELECT F1, F2, F3, F4, F5 FROM [sheet1$] WHERE F1 IS NOT NULL";

OleDbConnection connection = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + PrmPathExcelFile + @";Extended Properties=""Excel 8.0;IMEX=1;HDR=NO;TypeGuessRows=0;ImportMixedTypes=Text""");

OleDbCommand cmd = new OleDbCommand(sql, connection);
OleDbDataAdapter da = new OleDbDataAdapter(cmd);

DataSet ds = new DataSet();
ds.Tables.Add("xlsImport", "Excel");
da.Fill(ds, "xlsImport");

// Remove the first row (header row)
DataRow rowDel = ds.Tables["xlsImport"].Rows[0];
ds.Tables["xlsImport"].Rows.Remove(rowDel);

ds.Tables["xlsImport"].Columns[0].ColumnName = "LocationID";
ds.Tables["xlsImport"].Columns[1].ColumnName = "PartID";
ds.Tables["xlsImport"].Columns[2].ColumnName = "Qty";
ds.Tables["xlsImport"].Columns[3].ColumnName = "UserNotes";
ds.Tables["xlsImport"].Columns[4].ColumnName = "UserID";

connection.Close(); 

// vous pouvez maintenant utiliser LINQ pour effectuer une recherche dans les champs

    var data = ds.Tables["xlsImport"].AsEnumerable();
    var query = data.Where(x => x.Field<string>("LocationID") == "COOKCOUNTY").Select(x =>
                new Contact
                {
                    LocationID= x.Field<string>("LocationID"),
                    PartID = x.Field<string>("PartID"),
                    Quantity = x.Field<string>("Qty"),
                    Notes = x.Field<string>("UserNotes"),
                    UserID = x.Field<string>("UserID")
                });
101
Brian Wells

Plusieurs forums que j'ai trouvés affirment qu'en ajoutant IMEX=1;TypeGuessRows=0;ImportMixedTypes=Text aux propriétés étendues de la chaîne de connexion, le problème serait résolu, mais ce n'est pas le cas. J'ai finalement résolu ce problème en ajoutant "HDR = NO" aux propriétés étendues de la chaîne de connexion (comme le montre Brian Wells ci-dessus) afin de pouvoir importer des types mixtes.

J'ai ensuite ajouté un code générique pour nommer les colonnes après la première ligne de données, puis supprimer la première ligne.

    public static DataTable ImportMyDataTableFromExcel(string filePath)
    {
        DataTable dt = new DataTable();

        string fullPath = Path.GetFullPath(filePath);

        string connString =
           "Provider=Microsoft.Jet.OLEDB.4.0;" +
           "Data Source=\"" + fullPath + "\";" +
           "Extended Properties=\"Excel 8.0;HDR=No;IMEX=1;\"";

        string sql = @"SELECT * FROM [sheet1$]";

        using (OleDbDataAdapter dataAdapter = new OleDbDataAdapter(sql, connString))
        {
            dataAdapter.Fill(dt);
        }

        dt = BuildHeadersFromFirstRowThenRemoveFirstRow(dt);

        return dt;
    }

    private static DataTable BuildHeadersFromFirstRowThenRemoveFirstRow(DataTable dt)
    {
        DataRow firstRow = dt.Rows[0];

        for (int i = 0; i < dt.Columns.Count; i++)
        {
            if(!string.IsNullOrWhiteSpace(firstRow[i].ToString())) // handle empty cell
              dt.Columns[i].ColumnName = firstRow[i].ToString().Trim();
        }

        dt.Rows.RemoveAt(0);

        return dt;
    }
10
user1424725

Pas de problème sh4, content que cela aide w/le problème de type mixte.

La colonne DateTime est un autre animal que je me souviens qui m'a causé beaucoup de chagrin dans le passé ... nous avons un fichier Excel que nous traitons et que OleDbDataAdapter convertit parfois les dates en un type de données double (apparemment, Excel stocke les dates sous forme de doubles, qui encodent de jours écoulés depuis le 0 janvier 1900). 

La solution de contournement consistait à utiliser:

OleDbConnection mobjExcelConn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + txtExcelFile.Text + @";Extended Properties=""Excel 8.0;IMEX=1;HDR=Yes;""");

OleDbDataAdapter mobjExcelDataAdapter = new OleDbDataAdapter("Select * from [" + txtSheet.Text + "$] where [Supplier ID] <> '' ", mobjExcelConn);


DateTime dtShipStatus = DateTime.MinValue;
shipStatusOrig = excelRow["Est Ship Date"].ToString(); // excelRow is DataRow in the DataSet via the OleDbDataAdapter             

if (shipStatusOrig != string.Empty)
{
    // Date may be read in via oledb adapter as a double
    if (IsNumeric(shipStatusOrig))
    {
        double d = Convert.ToDouble(shipStatusOrig);
        dtShipStatus = DateTime.FromOADate(d);

        if (DateTime.TryParse(dtShipStatus.ToString(), out dtShipStatus))
        {
            validDate = true;
            Debug.WriteLine("{0} converted: ", dtShipStatus.ToString("s"));
        }
    }
    else
    {
        if (ValidateShipDate(shipStatusOrig))
        {
            dtShipStatus = DateTime.Parse(shipStatusOrig);
            validDate = true;
            Debug.WriteLine("{0} converted: ", dtShipStatus.ToString("s"));
        }
        else
        {
            validDate = false;
            MessageBox.Show("Invalid date format in the Excel spreadsheet.\nLine # " + progressBar1.Value + ", the 'Ship Status' value '" + shipStatusOrig + "' is invalid.\nDate should be in a valid date time format.\ne.g. M/DD/YY, M.D.Y, YYYY-MM-DD, etc.", "Invaid Ship Status Date");
        }
    }
...
}
        public static Boolean IsNumeric (Object Expression)
        {
            if(Expression == null || Expression is DateTime)
                return false;

            if(Expression is Int16 || Expression is Int32 || Expression is Int64 || Expression is Decimal || Expression is Single || Expression is Double || Expression is Boolean)
                return true;

            try
            {
                if(Expression is string)
                    Double.Parse(Expression as string);
                else
                   Double.Parse(Expression.ToString());
                return true;
            } catch {} // just dismiss errors but return false

            return false;
        }

        public bool ValidateShipDate(string shipStatus)
        {
            DateTime startDate;
            try
            {
                startDate = DateTime.Parse(shipStatus);
                return true;
            }
            catch
            {
                return false;
            }
        }
6
Brian Wells

Il existe deux manières de gérer des types de données mixtes et Excel.

Méthode 1

  • Ouvrez votre feuille de calcul Excel et définissez le format de colonne au format souhaité manuellement. Dans ce cas, "Texte".

Méthode 2

  • Il y a un "bidouille" qui consiste à ajouter "IMEX = 1" à votre chaîne de connexion like so:

    Fournisseur = Microsoft.Jet.OLEDB.4.0; Source de données = myfile.xls; Propriétés étendues = Excel 8.0; IMEX = 1

  • Cela tentera de gérer des formats Excel mixtes en fonction de la manière dont il est défini dans votre registre. Cela peut être défini localement par vous, mais pour un serveur, ce n'est probablement pas une option.

5
rlb.usa

@ Brian Wells Merci, votre suggestion a fait le tour, mais pas tout à fait ... Travaillé pour la chaîne mixte de champ mixte, mais les colonnes datetime ont des caractères étranges après cela, alors j'ai appliqué un "bidouillage" .

1.- Faites un System.Io.File.Copy et créez une copie du fichier Excel.

2.- Modifiez les en-têtes de colonne Datetime par programmation lors de l’exécution en un format au format datetime, c’est-à-dire "01/01/0001".

3.- Enregistrez Excel, puis appliquez votre astuce en faisant la requête avec HDR = NO au fichier modifié.

Tricky, oui, mais travaillé, et raisonnablement rapide, si quelqu'un a une alternative à cela, je serai heureux d'entendre.

Salutations.

P.D. Excusez mon anglais, ce n'est pas ma langue maternelle.

1
sh4

Raccourci -> si vous avez une colonne de type mixte dans Excel: triez votre colonne de Z à A

J'ai à peu près parcouru toutes les réponses ici et certaines d'entre elles ont fonctionné pour moi, mais pas toutes. Cependant, aucune solution n'était souhaitable pour moi, car d'une manière ou d'une autre ADO n'a pas sélectionné les données dans une colonne de type mixte que j'avais dans mon fichier Excel. J'ai dû définir HDR=NO pour que ADO lise la colonne de ma feuille de calcul qui est un mélange de texte et de chiffres. Ainsi, je perds la possibilité d'utiliser des en-têtes de colonne dans mes instructions SQL, ce qui n'est pas satisfaisant. Si l'ordre des colonnes change dans le fichier Excel, l'instruction SQL entraînera une erreur ou une sortie incorrecte. 

Dans une colonne de type de données mixte, la clé est constituée des 8 premières lignes. ADO détermine le type de données de la colonne en fonction des 8 premières lignes Donc, si vous souhaitez toujours modifier votre chaîne de connexion avec les paramètres étendus, il vous suffit de trier votre colonne Z en A sur votre fichier Excel avant de la lire. les données par ADO donc de cette façon les lignes en haut sont celles en texte et votre colonne sera alors sélectionnée en tant que texte. 

Si vos lignes initiales sont des nombres (peu importe si votre colonne est configurée pour formater TEXT dans Excel) ADO déterminera ces colonnes en tant que type numérique. Ainsi, une fois les lignes de texte lues ci-dessous, il ne pourra pas les convertir en nombre. . Dans le cas contraire, si la colonne contient du texte, s'il s'agit d'une ligne, d'un nombre, elle peut être convertie en texte.

0
Ibo