65.9K
CodeProject 正在变化。 阅读更多。
Home

数据库到 Excel 电子表格

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (8投票s)

2011年1月6日

CPOL

6分钟阅读

viewsIcon

88663

downloadIcon

5817

将 SQL 数据库中的数据选择到 Excel 电子表格,包含标题和格式设置

从 SQL 命令

到格式化的 Excel

引言

本项目演示了如何根据 SQL SELECT 命令的结果创建格式化的 Excel 工作表。生成的 Excel 电子表格包含基于 SQL 结果返回的列的格式化、可排序和可筛选的标题。列可以在电子表格中格式化为文本或货币。用户可以在同一个 Excel 工作簿中创建多个工作表,其中包含来自不同 SQL 命令或数据库的结果。工作表的列标题取决于 SQL 语句的结果,更改 Select 语句,Excel 列也会随之改变。

我创建这个项目是为了能够快速以商业友好、专业的方式生成临时信息,而无需为每个请求编写代码。

要求

本项目是使用 Visual Studio 2010 和 Office Excel 2007 生成的。部分代码使用了 VS 2010 中添加的功能,部分 Excel 命令需要 2007 版 Office。这些概念可以与早期版本的 VS 和 Office 配合使用,只需对程序进行一些修改。此程序使用 SQL 特定的 ADO 命令,但我认为它也可以与通用的 ODBC 版本命令配合使用。

这个问题应该可以在任何安装了 Framework V4.0、Office 2007 并可以访问 SQL 数据库的机器上运行。

操作概述

  • 用户输入连接字符串、SQL 命令和工作表名称。
  • 代码将访问数据库并根据结果构建一个 DataTable
  • 代码然后从 DataTable 列数据生成 Excel 标题。
  • 代码将 DataTable 中的 DataRows 添加到 Excel 工作簿。
  • 用户可以根据需要保存或放弃电子表格。

程序

我使用 WPF 作为用户界面创建了这个项目。界面很简单:用户输入连接字符串、SQL 命令、工作表名称,然后按“导出到 Excel”按钮。

从 SQL 构建 DataTable

点击事件处理程序对输入的数据进行最少的验证,然后调用 PerformSqlGet() 通过 ADO.NET 从数据库返回 DataTable

conn = new SqlConnection(_ConnectionString);
cmd = new SqlCommand(_Command);

此命令的类型(StoredProcedureText)基于命令的第一个单词。创建适配器来处理 Command,并使用适配器填充 DataTable

   cmd.Connection = conn;
   cmd.CommandTimeout = _Timeout; 
   adapter = new SqlDataAdapter(cmd);
   adapter.Fill(dt);

加载 Excel

第一次按下此按钮时,Excel 会在后台加载。与 Excel 的通信是通过调用 COM 互操作来实现的,但现在编写这些代码似乎比以前版本的 Excel 和 Visual Studio 容易得多。我没有定位和下载 Excel 的 Primary Interop Assembly,而是直接为我的程序添加了一个 Excel.exe 的引用(在本例中是:c:\Program Files\Microsoft Office\Office12\EXCEL.EXE)。这为我生成了 interop assembly。在我的代码中,我添加了

using Microsoft.Office.Interop.Excel;

并声明了一些类变量

Microsoft.Office.Interop.Excel.Application _ExcelApp = null;
Microsoft.Office.Interop.Excel.Workbook _wb = null;

现在我已准备好加载 Excel。

在第一次按下“导出到 Excel”按钮时,我加载 Excel 应用程序,创建一个工作簿,并向工作簿添加一个工作表。

_ExcelApp = new Microsoft.Office.Interop.Excel.Application();
_wb = _ExcelApp.Workbooks.Add();
Worksheet ws = AddWorksheetToWorkbook(_wb, txtWorksheetName.Text);

_ExcepApp 是后台运行的 Excel 应用程序实例的指针。Worksheet(也称为 Microsoft.Office.Interop.Excel.WorkSheet)是我将要向其中添加 DataTable 数据的 Excel 表格。

加载 Excel 工作表

完成后,我调用我的 AddToExcel() 方法来执行将标题和数据添加到工作表的任务。在 AddToExcel() 中,我使 Excel 不可见以获得轻微的性能提升。然后,我将 DataTable 中的列名添加到 Excel 的第一行。

for (int Idx = 0; Idx < dt.Columns.Count; Idx++)
{
     ws.Range["A1"].Offset[0, Idx].Value = dt.Columns[Idx].ColumnName;
}

这样就创建了 Excel 标题。现在,我将 DataTable 中每一行的数据添加到电子表格中。

for (int Idx = 0; Idx < dt.Rows.Count; Idx++)
{  // <small>hey! I did not invent this line of code, 
   // I found it somewhere on CodeProject.</small> 
   // <small>It works to add the whole row at once, pretty cool huh?</small>
   ws.Range["A2"].Offset[Idx].Resize[1, dt.Columns.Count].Value = dt.Rows[Idx].ItemArray;
}

现在标题和数据行已加载。有一个 Excel 2007 函数可以将工作表格式化为表格。要使用它,您需要定义一个 Excel 范围并调用该函数来创建表格。格式化表格的代码如下:

string EndColumn = GetColumnName(dt.Columns.Count);
Microsoft.Office.Interop.Excel.Range r = ws.Range["A1:" + EndColumn 
    + (dt.Rows.Count + 1).ToString()];
r.Select();
ws.ListObjects.Add(XlListObjectSourceType.xlSrcRange, r, null
    , XlYesNoGuess.xlYes).Name = "Table1";

结果是一个格式精美的表格,带有彩色线条,并且可以按列数据进行排序或筛选。

收尾工作

列格式化

此版本的程序将允许将命名列格式化为文本或货币,而不是使用 Excel 的默认单元格格式。例如,此示例返回一个 PostalCode 列,其中包含数字和字母。默认的 Excel 显示效果不好

DataTable 列名(在此例中为 'PostalCode')添加到“String Columns”字段会改变该列在 Excel 中的显示方式。

具有匹配名称的 Excel 列被重新格式化为“text”格式,从而获得更好的显示效果。

实现此目的所使用的技术是检查 DataTable 中的每一列,看其列名是否在用户界面中。如果匹配,则将“Text”格式应用于该列。奇怪的是,“Text”格式实际上是数字格式选项。

for (int Idx = 0; Idx < dt.Columns.Count; Idx++)   
{   
    // if this column name is in the list of string request    
    if (ColumnsToMakeString != null)   
    {   
        if (ColumnsToMakeString.Contains(dt.Columns[Idx].ColumnName))   
        {   
            string ColumnName = GetColumnName(Idx + 1);   
            ColumnName += ":" + ColumnName;   
            ws.Range[ColumnName].NumberFormat = "@";    
        }   
    }   
    if (ColumnsToMakeCurrency != null)   
    {   
        // if this column name is in the list of currency request    
        if (ColumnsToMakeCurrency.Contains(dt.Columns[Idx].ColumnName))   
        {   
            string ColumnName = GetColumnName(Idx + 1);   
            ColumnName += ":" + ColumnName;   
            ws.Range[ColumnName].NumberFormat = "$#,##0.00";   
        }   
    }   
}   

数字到 Excel 列

上面使用的 GetColumnName() 方法是为了将数字转换为 Excel 的列名而开发的。例如,它将 1 转换为列名“A”,2 转换为“B”,26 转换为“AA”,27 转换为“BB”,依此类推。我通过简单地索引一对数组来完成此操作。

private string GetColumnName(int Column) 
{ 
    if (Column < 1) 
    { 
        return "A"; 
    } 
    if (Column > 26 * 27) 
    { 
        return "ZZ"; 
    }
    char[] Alphabet = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'
                        , 'I', 'J', 'K' , 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S'
                        , 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; 
    char[] Leader = new char[] { ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
                        , 'H', 'I', 'J', 'K' , 'L', 'M', 'N', 'O', 'P', 'Q', 'R'
                        , 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; 

    int IdxLeader = (Column - 1) / 26; 
    string results = Leader[IdxLeader].ToString()  
                + Alphabet[(Column - IdxLeader * 26) - 1].ToString(); 
    return results.Trim(); 
} 

注意DataTable 和 C# 的工作是相对于零的,而 Excel 的工作是相对于 1 的,因此调用 GetColumnName() 时使用了“Idx+1”。

Excel 列宽

我还担心数据字段过宽的问题。在我的一些生产表中,数据宽度可能高达 2000 个字符。我想限制 Excel 中列的宽度。另一方面,我也不希望宽列中有空白。代码处理每一列,首先设置“AutoFit”以使宽度适合所有数据,然后将任何列的最大宽度设置为 80 个点。结果是列足够宽以容纳数据,并在 80 个点处换行。我不确定这个度量单位是什么,但我知道它不是像素。

for (int Idx = 0; Idx < dt.Columns.Count; Idx++)   
{   
    string ColumnName = GetColumnName(Idx + 1);   
    string RangeName = ColumnName + ":" + ColumnName; // results something like "B:B"    
    ws.Range[RangeName].EntireColumn.AutoFit();   
    if (ws.Range[RangeName].ColumnWidth > 80)   
    {   
        ws.Range[RangeName].ColumnWidth = 80;   
    }   
}   

多个工作表

我想为来自多个表或数据库的数据在同一个 Excel 工作簿中创建多个工作表。为了实现这一点,我只打开一次 Excel,并为每个 SQL 命令重复使用该 Excel。当我们在这些 SQL 命令之间关闭 Excel 时,这会带来问题。如果 Excel 关闭并且发出另一个 SQL 命令,那么 _ExcelApp 中指向已关闭的 Excel 的指针就会失效。为了解决这个问题,我在 AddWorksheetToWorkBook() 上捕获了一个异常。如果失败,则假定 Excel 已关闭并打开一个新的 Excel 实例。这不是一个很好的解决方案,但目前是我能做到的最好的。

实现这一目标的代码位于按钮的点击事件处理程序中。

if (_ExcelApp == null)   
{   
    _ExcelApp = new Microsoft.Office.Interop.Excel.Application();   
    _wb = _ExcelApp.Workbooks.Add();   
}   
   
// Add a worksheet    
Worksheet ws;    
try   
{   
    ws = AddWorksheetToWorkbook(_wb, txtWorksheetName.Text);    
}   
catch (Exception ex)   
{   // The main reason the AddWorksheet fails is because the user closed    
    // Excel between sheet creation   
    // so, create a new Excel and workbook    
    _ExcelApp = new Microsoft.Office.Interop.Excel.Application();   
    _wb = _ExcelApp.Workbooks.Add();   
    ws = AddWorksheetToWorkbook(_wb, txtWorksheetName.Text);    
}   

结论

这段代码还不够完美。错误处理和恢复可以做得更好一些,并且目前它无法处理存储过程的参数。但它是一种准备 Excel 文档以支持临时报告和信息的快速方法。

历史

  • 2011年1月6日:初始发布

© . All rights reserved.