通过C#从Excel文件导入常规格式化单元格

时间:2016-12-29 14:39:21

标签: c# .net excel import datatable

我目前使用:

  • Visual Studio 2015 Update 3
  • Microsoft Office Professional Plus 2013
  • .Net Framework 4.5.1
  • Windows 7 64位

我正在使用以下代码将Excel工作表读入DataTable:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.Data.SqlClient;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

string filename = "C:\\Users\\myusername\\Documents\\MyFile.xlsx";

DataTable dt = null;

try
{
    string ExcelName = filename.Split(("\\").ToCharArray()[0])[filename.Split(("\\").ToCharArray()[0]).Length - 1].Split('.')[0];

    string ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filename + ";Extended Properties=\"Excel 12.0;HDR=YES;IMEX=1;TypeGuessRows=0;ImportMixedTypes=Text\";";

    string SheetName = CommonFunctions.GetExcelSheetNames(ConnectionString)[0];

    using (OleDbConnection conn = new OleDbConnection(ConnectionString))
    {
        string query = string.Format("SELECT * FROM [" + SheetName + "]");

        conn.Open();

        using (OleDbCommand cmd = new OleDbCommand(query, conn))
        {
            using (OleDbDataReader rdr = cmd.ExecuteReader())
            {
                if (rdr.HasRows)
                {
                    dt = new DataTable();

                    dt.TableName = ExcelName;

                    for (int i = 0; i < rdr.FieldCount; i++)
                    {
                        dt.Columns.Add(new DataColumn(rdr.GetName(i), typeof(string)));

                    }

                    while (rdr.Read())
                    {
                        DataRow dr = dt.NewRow();

                        for (int i = 0; i < rdr.FieldCount; i++)
                        {
                            dr[i] = rdr[i].ToString();

                        }

                        dt.Rows.Add(dr);

                    }

                    foreach (DataRow row in dt.Rows)
                    {
                        string s = row[0].ToString();

                    }

                }

            }

        }

    }

}
catch (Exception ex)
{
    MessageBox.Show(ex.StackTrace, ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error);

}

示例Excel工作表数据如下所示,以CSV格式提供,以便在Excel中轻松打开:

"store_number", "stock_code", "desired_quantity"
"64004", "BI_KRA_SEL_350065", "1"
"64004", "BI_KRA_SEL_500080", "1"
"86208", "BI_KRA_SEL_350065", "1"
"86208", "BI_KRA_SEL_500080", "1"
"64019", "BI_KRA_SEL_350065", "1"
"64019", "BI_KRA_SEL_500080", "1"
"85858", "BI_KRA_SEL_350065", "1"
"85858", "BI_KRA_SEL_500080", "1"
"72122", "BI_KRA_SEL_350065", "1"
"72122", "BI_KRA_SEL_500080", "1"
"68427", "BI_KRA_SEL_350065", "1"
"68427", "BI_KRA_SEL_500080", "1"
"79031", "BI_KRA_SEL_350065", "1"
"79031", "BI_KRA_SEL_500080", "1"
"67662", "BI_KRA_SEL_350065", "1"
"67662", "BI_KRA_SEL_500080", "1"
"92246", "BI_KRA_SEL_350065", "1"
"92246", "BI_KRA_SEL_500080", "1"
"85432", "BI_KRA_SEL_350065", "1"
"85432", "BI_KRA_SEL_500080", "1"
"87188", "BI_KRA_SEL_350065", "1"
"87188", "BI_KRA_SEL_500080", "1"
"91021", "BI_KRA_SEL_350065", "1"
"91021", "BI_KRA_SEL_500080", "1"
"79022", "BI_KRA_SEL_350065", "1"
"79022", "BI_KRA_SEL_500080", "1"
"86369", "BI_KRA_SEL_350065", "1"
"86369", "BI_KRA_SEL_500080", "1"
"67670", "BI_KRA_SEL_350065", "1"
"67670", "BI_KRA_SEL_500080", "1"
"92605", "BI_KRA_SEL_350065", "1"
"92605", "BI_KRA_SEL_500080", "1"
"92609", "BI_KRA_SEL_350065", "1"
"92609", "BI_KRA_SEL_500080", "1"
"92610", "BI_KRA_SEL_350065", "1"
"92610", "BI_KRA_SEL_500080", "1"
"92611", "BI_KRA_SEL_350065", "1"
"92611", "BI_KRA_SEL_500080", "1"
"92612", "BI_KRA_SEL_350065", "1"
"92612", "BI_KRA_SEL_500080", "1"
"92613", "BI_KRA_SEL_350065", "1"
"92613", "BI_KRA_SEL_500080", "1"
"92614", "BI_KRA_SEL_350065", "1"
"92614", "BI_KRA_SEL_500080", "1"
"92615", "BI_KRA_SEL_350065", "1"
"92615", "BI_KRA_SEL_500080", "1"
"92616", "BI_KRA_SEL_350065", "1"
"92616", "BI_KRA_SEL_500080", "1"
"w090", "BI_KRA_SEL_350065", "1"
"w090", "BI_KRA_SEL_500080", "1"
"C908", "BI_KRA_SEL_350065", "1"
"C908", "BI_KRA_SEL_500080", "1"
"w0901", "BI_KRA_SEL_350065", "1"
"w0901", "BI_KRA_SEL_500080", "1"
"G202", "BI_KRA_SEL_350065", "1"
"G202", "BI_KRA_SEL_500080", "1"

问题是第一列包含字母和/或空格。这些单元格在结果DataTable(dt)中显示为空白,即从“w090”到“G202”。

我发现当单元格格式为“常规”时会发生这种情况。但是,将这些单元格的格式更改为“文本”似乎可以解决问题。

我现在唯一的问题是我不能依赖我的客户提供将单元格设置为“文本”格式的文件。

是否有人知道这方面的修复方法,或者可能是使用“文字”格式克隆Excel文件的方法?

也许有人知道将Excel文件导入DataTables / DataSet的更智能方法。

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:2)

由于您使用Excel作为数据库,因此每个字段(列)必须具有其确切的数据类型。 Excel数据库驱动程序从第一个值猜测此类型。在您的情况下,第一列中的第一个值是数字。所以数据库在那里猜测数字数据类型。所以后来出现的字符串不适合那种类型。

数据库驱动程序有一个参数IMEX,可以将所有数据视为文本。请参阅https://www.connectionstrings.com/ace-oledb-12-0/treating-data-as-text/

所以试试

string connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filename + ";Extended Properties=\"Excel 12.0;IMEX=1\";";

string connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filename + ";Extended Properties=\"Excel 12.0;HDR=NO;IMEX=1\";";

答案 1 :(得分:1)

这比使用adapter.Fill()要多一点,但是如果你想对数据类型有更多的控制权,你可以预先声明它们,然后一次填充一行DataTable。因为它的Excel和Excel会很乐意混合和匹配列中的数据类型,所以我认为OleDb几乎不可能预先确定正确的数据类型。

以下是如何显式声明数据类型然后手动将其插入数据表的示例:

OleDbCommand cmd = new OleDbCommand(query, conn);
OleDbDataReader reader = cmd.ExecuteReader();

object[] fields = new object[reader.FieldCount];
for (int i = 0; i < fields.Length; i++)
    dt.Columns.Add(new DataColumn(reader.GetName(i)));

dt.Columns[0].DataType = typeof(string);
dt.Columns[1].DataType = typeof(string);
dt.Columns[2].DataType = typeof(int);

while (reader.Read())
{
    reader.GetValues(fields);
    dt.Rows.Add(fields);
}

reader.Close();

- 编辑1/3/2017 -

以下是使用POCO的解决方案,我相信它会起作用:

如果您的POCO看起来像这样:

public class Stock
{
    public string StoreNumber { get; set; }
    public string StockCode { get; set; }
    public double DesiredQuantity { get; set; }
}

此代码应从Excel读取数据并将其放入域对象列表中:

OleDbConnection conn = new OleDbConnection(ConnectionString);
conn.Open();

OleDbCommand cmd = new OleDbCommand(query, conn);
OleDbDataReader reader = cmd.ExecuteReader();

List<Stock> stockData = new List<Stock>();

while (reader.Read())
{
    stockData.Add(new Stock()
    {
        StoreNumber = reader.GetValue(0).ToString(),
        StockCode = reader.GetValue(1).ToString(),
        DesiredQuantity = reader.GetDouble(2)
    });
}

reader.Close();

我认为.GetString(x)可能会因为您突出显示的问题而引发错误,但是通过使用.GetValue(x).ToString(),您可以强制数据类型,知道它们都应该是字符串。

从这里开始,与数据表相比,我认为使用List<Stock>将是一种乐趣。最好的部分是你对数据进行全面控制。

答案 2 :(得分:0)

使用 Interop 服务,这是一个很好的可靠解决方案。

添加引用: Microsoft.Office.Interop.Excel版本12.0.0.0,可通过Nuget获取。

注意:请务必将完整路径作为文件名参数传递。

public static DataTable LoadExcelFile(string fileName, string worksheetName, int headerRowNumber, int firstDataRowNumber)
{
    DataTable dt = new DataTable();

    Microsoft.Office.Interop.Excel.Application ExcelApplication = new Microsoft.Office.Interop.Excel.Application();

    Microsoft.Office.Interop.Excel.Workbook ExcelWorkbook = ExcelApplication.Workbooks.Open(fileName, 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);

    Microsoft.Office.Interop.Excel.Worksheet ExcelWorksheet = null;

    string WorksheetName = worksheetName;

    if (string.IsNullOrWhiteSpace(worksheetName))
    {
        WorksheetName = ExcelWorkbook.ActiveSheet.Name;

    }

    ExcelWorksheet = (Microsoft.Office.Interop.Excel.Worksheet)ExcelWorkbook.Worksheets[WorksheetName];

    dt.TableName = WorksheetName;

    // Add the columns

    Dictionary<string, int> Columns = new Dictionary<string, int>();

    for (int i = 0; i < ExcelWorksheet.UsedRange.Columns.Count; i++)
    {
        string ColumnHeading = Convert.ToString(((Microsoft.Office.Interop.Excel.Range)ExcelWorksheet.Cells[headerRowNumber, i + 1]).Value2);

        if (!String.IsNullOrWhiteSpace(ColumnHeading) && !dt.Columns.Contains(ColumnHeading))
        {
            Columns.Add(ColumnHeading, i + 1);

            dt.Columns.Add(ColumnHeading);

        }

    }

    // Add the rows

    for (int i = 0; i < ExcelWorksheet.UsedRange.Rows.Count - firstDataRowNumber + 1; i++)
    {
        try
        {
            int ColumnCount = 0;

            DataRow Row = dt.NewRow();

            bool RowHasContent = false;

            foreach (KeyValuePair<string, int> kvp in Columns)
            {
                string CellContent = Convert.ToString(((Microsoft.Office.Interop.Excel.Range)ExcelWorksheet.Cells[i + firstDataRowNumber, kvp.Value]).Value2);

                Row[ColumnCount] = CellContent;

                ColumnCount++;

                if (!string.IsNullOrWhiteSpace(CellContent))
                {
                    RowHasContent = true;

                }

            }

            if (RowHasContent)
            {
                dt.Rows.Add(Row); ;

            }

        }
        catch
        {

        }

    }

    // Clean up

    try { ExcelWorksheet = null; } catch { }

    try { ExcelWorkbook.Close(); } catch { }

    try { ExcelWorkbook = null; } catch { }

    try { ExcelApplication = null; } catch { }

    return dt;
}

为什么要使用Interop服务?

使用基于JetAce数据库引擎的解决方案时,Interop服务可以解决类型猜测和格式化错误。

错误解决方案的示例如下:

  • ExcelDataReader
  • LinqToExcel
  • 的OleDb

如果您目前使用上述解决方案之一,那么值得测试错误,请将成功的解决方案发布到此页面。

令人愉快的惊喜

使用Interop解决方案时,我预计在Excel中打开的文件与程序中打开的文件同名时会出现问题,但在测试期间我没有遇到过这样的问题。