为CLR过程添加程序集

时间:2012-06-18 20:42:33

标签: c# sql-server interop clr

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

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

如何在我的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);
        }
    }
}
}

3 个答案:

答案 0 :(得分:3)

在SQL Server中无法使用任意程序集。您只需引用框架的subset并执行一些basic discipline。我怀疑具有Excel历史层的怪物应用程序是否会加载到此环境中。

您可能需要查看符合此条件的更简单的substitute functionality

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

答案 1 :(得分:1)

经过一天的摸索,几乎每种类型的错误都可能发生,我想出了解决问题的方法。

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

在我对煤炭进行倾斜之前,请注意此项目绝对需要在更传统的SQL Server导出功能范围之外进行自动过滤和其他格式化。

<强>解决方案

我创建了一个名为SqlProcedures的类库输出类型visual studio项目,创建了一个名为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

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

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

答案 2 :(得分:0)

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