2

我正在尝试创建一个 CLR 过程来将 SQL 数据导出到 Excel,该过程将包含比其他选项(例如小计和突出显示)更多的功能。

这需要我引用Microsoft.Office.Interop.Exceldll,但我不确定在编译代码时如何实际包含程序集。

如何在我的 CLR 过程中包含 Excel 程序集?

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using Excel = Microsoft.Office.Interop.Excel;
using System.Reflection;

public class ExportToExcel
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void ExportQueryResults(string queryText, string worksheetName, string fileName)
{
    using (SqlConnection cnn = new SqlConnection("context connection=true"))
    {
        //the temp list to hold the results in
        List<object[]> results = new List<object[]>();

        cnn.Open();
        //create the sql command
        SqlCommand cmd = new SqlCommand(queryText, cnn);
        using (SqlDataReader reader = cmd.ExecuteReader())
        {
            int fieldCount = reader.FieldCount;
            object[] headers = new object[fieldCount];
            for(int i = 0; i < fieldCount; i++)
            {
                headers[i] = reader.GetName(i);
            }

            //read the results
            while (reader.Read())
            {
                object[] values = new object[fieldCount];
                for (int i = 0; i < fieldCount; i++)
                {
                    values[i] = reader[i];
                }
                results.Add(values);
            }

            //convert the results into a 2-d array to export into Excel
            object[,] exportVals = new object[results.Count, fieldCount];

            for (int row = 0; row < results.Count; row++)
            {
                for (int col = 0; col < fieldCount; col++)
                {
                    exportVals[row, col] = results[row][col];
                }
            }

            Excel.Application _app = new Excel.Application();
            Excel.Workbook _book = _app.Workbooks.Add(Missing.Value);
            Excel.Worksheet _sheet = (Excel.Worksheet)_book.ActiveSheet;
            Excel.Range _range = (Excel.Range)_sheet.Cells[1, 1];

            _range = _sheet.get_Range(_sheet.Cells[1, 1], _sheet.Cells[results.Count, fieldCount]);
            _range.Value2 = exportVals;

            _sheet.Name = worksheetName;

            //remove any extra worksheets
            foreach(Excel.Worksheet sht in _book.Worksheets)
            {
                if (sht.Name != worksheetName)
                    sht.Delete();
            }

            _book.SaveAs(fileName
                , Excel.XlFileFormat.xlWorkbookDefault
                , Missing.Value
                , Missing.Value
                , false
                , false
                , Excel.XlSaveAsAccessMode.xlNoChange
                , Missing.Value
                , Missing.Value
                , Missing.Value
                , Missing.Value
                , Missing.Value);
        }
    }
}
}
4

3 回答 3

3

在 SQL Server 中不能使用任意程序集。您只需要参考框架的一个子集并练习一些基本规则。我怀疑像 Excel 这样具有多层历史的怪物应用程序会加载到这个环境中。

您可能希望查看符合此标准的更简单的替代功能。

如果您需要更多,请考虑在客户端使用 Excel。

于 2012-06-18T20:56:35.130 回答
1

经过一天的摸索几乎所有类型的错误后,我想出了一个解决我的问题的方法。

我很欣赏给出的答案/评论,虽然我同意可能有更有效/更安全的方法来实施解决方案,但使用互操作程序集是完成这个项目的最快和最熟悉的方法。

在我开始讨论之前,请意识到这个项目绝对需要自动过滤和其他更传统的 SQL Server 导出功能范围之外的格式。

解决方案

我创建了一个名为 的类库输出类型 Visual Studio 项目SqlProcedures,创建了一个名为的新类ExportToExcel并将其添加Microsoft.Office.Interop.Excel到我的引用中。

这是我ExportToExcel.cs文件中的代码:

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using Excel = Microsoft.Office.Interop.Excel;
using System.Reflection;
using System.Runtime.InteropServices;

public class ExportToExcel
{
    [Microsoft.SqlServer.Server.SqlProcedure]
public static void ExportQueryResults(string queryText, string worksheetName, string fileName)
{
    using (SqlConnection cnn = new SqlConnection("context connection=true"))
    {
        //the temp list to hold the results in
        List<object[]> results = new List<object[]>();

        cnn.Open();
        //create the sql command
        SqlCommand cmd = new SqlCommand(queryText, cnn);
        using (SqlDataReader reader = cmd.ExecuteReader())
        {
            int fieldCount = reader.FieldCount;
            object[] headers = new object[fieldCount];
            for (int i = 0; i < fieldCount; i++)
            {
                headers[i] = reader.GetName(i);
            }

            //read the results
            while (reader.Read())
            {
                object[] values = new object[fieldCount];
                for (int i = 0; i < fieldCount; i++)
                {
                    values[i] = reader[i];
                }
                results.Add(values);
            }

            //convert the results into a 2-d array to export into Excel
            object[,] exportVals = new object[results.Count, fieldCount];

            for (int row = 0; row < results.Count; row++)
            {
                for (int col = 0; col < fieldCount; col++)
                {
                    exportVals[row, col] = results[row][col];
                }
            }

            Excel.Application _app = new Excel.Application();
            Excel.Workbook _book = _app.Workbooks.Add(Missing.Value);
            Excel.Worksheet _sheet = (Excel.Worksheet)_book.ActiveSheet;
            Excel.Range _range = (Excel.Range)_sheet.Cells[1, 1];
            _app.DisplayAlerts = false;

            //set the headers and freeze the panes
            _range = _sheet.get_Range(_sheet.Cells[1, 1], _sheet.Cells[1, fieldCount]);
            _range.NumberFormat = "@";
            _range.HorizontalAlignment = Excel.XlHAlign.xlHAlignLeft;
            _range.Value2 = headers;
            _range.Font.Bold = true;
            _range = _sheet.get_Range(_sheet.Cells[2, 1], _sheet.Cells[2, 1]);
            _range.EntireRow.Select();
            _range.Application.ActiveWindow.FreezePanes = true;

            _range = _sheet.get_Range(_sheet.Cells[2, 1], _sheet.Cells[results.Count, fieldCount]);
            _range.Value2 = exportVals;

            _range = _sheet.get_Range(_sheet.Cells[1, 1], _sheet.Cells[exportVals.Length, fieldCount]);
            _range.AutoFilter(1, Type.Missing, Excel.XlAutoFilterOperator.xlAnd, Type.Missing, true);
            _sheet.Cells.Columns.AutoFit();
            _sheet.Range["A1"].Select();

            _sheet.Name = worksheetName;

            //remove any extra worksheets
            foreach (Excel.Worksheet sht in _book.Worksheets)
            {
                if (sht.Name != worksheetName)
                    sht.Delete();
            }

            _book.SaveAs(fileName
                , Excel.XlFileFormat.xlExcel5
                , Missing.Value
                , Missing.Value
                , false
                , false
                , Excel.XlSaveAsAccessMode.xlNoChange
                , Missing.Value
                , Missing.Value
                , Missing.Value
                , Missing.Value
                , Missing.Value);

            //_book.Close(Missing.Value, Missing.Value, Missing.Value);
            _app.Application.Quit();

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Marshal.ReleaseComObject(_range);
            Marshal.ReleaseComObject(_sheet);
            Marshal.ReleaseComObject(_book);
            Marshal.ReleaseComObject(_app);

            _range = null;
            _sheet = null;
            _book = null;
            _app = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
}
}

成功构建 DLL 后,我将其复制到我的 SQL 服务器上的本地目录中。

为了运行该过程ExportQueryResults,我需要在Microsoft.Office.Interop.Excel.dll依赖的 SQL 服务器中添加几个程序集。

这是我的 SQL 代码:

ALTER DATABASE main SET TRUSTWORTHY ON;

create assembly [stdole] from
'C:\Program Files\Microsoft.NET\Primary Interop Assemblies\stdole.dll'
WITH PERMISSION_SET = unsafe
create assembly [Office] from
'C:\WINDOWS\assembly\GAC\office\12.0.0.0__71e9bce111e9429c\OFFICE.DLL'
WITH PERMISSION_SET = unsafe

create assembly [Vbe] 
    FROM 'C:\WINDOWS\assembly\GAC\Microsoft.Vbe.Interop\12.0.0.0__71e9bce111e9429c\Microsoft.Vbe.Interop.dll'
WITH PERMISSION_SET = unsafe


create assembly [Microsoft.Office.Interop.Excel.dll] 
from 'C:\WINDOWS\assembly\GAC\Microsoft.Office.Interop.Excel\12.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.Excel.dll'
WITH PERMISSION_SET = unsafe

create assembly SqlProcedures from 'c:\sql_data_reporting\SqlProcedures.dll'
WITH PERMISSION_SET = unsafe
go
create procedure ExportToExcel @queryText nvarchar(4000), @worksheetName nvarchar(32),     @fileName nvarchar(250)
as external name SqlProcedures.ExportToExcel.ExportQueryResults
go

现在我知道 usingwith permission_set = unsafe是一个 nono,但这是一个“立即完成”项目,这是我能想到的最快的解决方案。

希望这个解决方案能为其他需要实现类似功能的人节省一些时间。

于 2012-06-19T17:56:20.767 回答
0

我相信您可以像添加核心程序集一样简单地添加引用的程序集。问题是互操作程序集是 Excel COM 对象的一个​​薄包装器。这意味着除非您还在 SQL Server 上安装 Microsoft Excel,否则互操作将毫无价值。我什至不确定这是否可能,但这听起来是一个非常非常糟糕的主意。

于 2012-06-18T21:03:13.177 回答