【问题标题】:"Cannot load dynamically generated serialization assembly" error in SQLCLR stored procedureSQLCLR 存储过程中的“无法加载动态生成的序列化程序集”错误
【发布时间】:2015-10-12 18:41:39
【问题描述】:

当我尝试从 SQL Server 2008 R2 执行 C# CLR 存储过程时出现以下错误:

消息 6522,级别 16,状态 1,过程 PrintShippingDocLabelsToPDF,第 0 行
在执行用户定义的例程或聚合“PrintShippingDocLabelsToPDF”期间发生 .NET Framework 错误:
System.InvalidOperationException:无法加载动态生成的序列化程序集。在某些托管环境中,程序集加载功能受到限制,请考虑使用预生成的序列化程序。

请参阅内部异常了解更多信息。 --->

System.IO.FileLoadException:LoadFrom()、LoadFile()、Load(byte[]) 和 LoadModule() 已被主机禁用。
System.IO.FileLoadException:

据我了解,这正是因为我试图 写入网络共享上的文件。我查看了以下来源:https://msdn.microsoft.com/en-us/library/ms345106(v=sql.105).aspxAccessing Sql FILESTREAM from within a CLR stored procedure,但我仍然迷路了。

我需要做的就是将几个报告从 SSRS 写入网络共享上的 xxx.pdf 文件(其中 xxx 代表自定义名称)。此功能的代码如下,我相当肯定下面的部分会导致错误。

// Create a file stream and write the report to it
using (FileStream stream = File.OpenWrite(strFilePath + strFileName))
{
    stream.Write(results, 0, results.Length);
} // END using (FileStream stream = File.OpenWrite(StoredProcedures.strFilePath + StoredProcedures.strFileName))

这是完整的过程。有人可以给我指点吗?我的工作期限很紧。作为临时解决方案,也许可以将这些报告写入临时表,然后使用 SSIS 包将它们移动到文件夹中。如果可能的话,有人可以告诉我如何存储这些文件(varbinary(这些文件的最大大小约为 60KB-150KB)?

public static void PrintShippingDocLabelsToPDF()
{
    // Put your code here  
    using (SqlConnection sqlConnTestDB = new SqlConnection("context connection=true"))
    {
        string sql4ConnectionString = "connection string";

        string sqlConnStrTestDB = sql4ConnectionString;

        // Select all unprinted SPSNumbers
        // If IsValidatedByWms == 0 --> SPS Not printed
        // If IsValidatedByWms == 1 --> SPS is printed
        string sqlCmdSlctTblPicking = @"SELECT SPSNumber, IsValidatedByWms AS LabelPrintStatus 
                                      FROM TestDB.dbo.tblPickingSlip WHERE IsValidatedByWms = 0";

        //SqlConnection sqlConnTestDB = new SqlConnection(sqlConnStrTestDB);
        SqlCommand sqlCmdSlctPicking = new SqlCommand(sqlCmdSlctTblPicking, sqlConnTestDB);

        // Select all SPSNumbers that have invoices against them
        // SPS number will be coming from tblPicking
        // If SPSNumber is NULL --> No invoice against SPS
        // If SPSNumber is NOT NULL --> There is an invoice against the SPS
        string sqlCmdStrSlctTblInvoiceItem = @"SELECT DISTINCT SPSNumber 
                                              FROM TestDB.dbo.tblInvoiceItem
                                              WHERE SPSNumber IS NOT NULL ";

        SqlCommand sqlCmdSlctTblInvoiceItem = new SqlCommand(sqlCmdStrSlctTblInvoiceItem, sqlConnTestDB);

        DataTable dtPicking = new DataTable();
        SqlDataAdapter sqlPickingAdapter = new SqlDataAdapter(sqlCmdSlctPicking);
        sqlPickingAdapter.Fill(dtPicking);

        DataTable dtTblInvoiceItem = new DataTable();
        SqlDataAdapter sqlTblInvoiceItemAdapter = new SqlDataAdapter();

        // Update print status of printed lables, labels only print for SPSNumbers that have invoices against them
        string sqlCmdStrUpdate = @"UPDATE TestDB.dbo.tblPickingSlip 
                                   SET IsValidatedByWms = 1
                                   WHERE SPSNumber = ";

        SqlCommand sqlCmdUpdateSlctPicking = new SqlCommand(sqlCmdStrUpdate, sqlConnTestDB);

        string strSpsNumber = null;  // keep track of the SPSNumber

        // Inspect the Picking table
        foreach (DataRow row in dtPicking.Rows)
        {
            if (Convert.ToInt32(row["LabelPrintStatus"]) == 0)
            {
                // a label has not been printed for the associated SPSNumber
                // check if the particualr SPSNumber has an assocaited invoice
                strSpsNumber = row["SPSNumber"].ToString();                    

                // add SPSNumber to query that selects all SPSNumbers that
                // have invoices against them
                string sqlCmdStrSlctTblInvoiceItem2 = sqlCmdStrSlctTblInvoiceItem + "AND SPSNumber = '" + strSpsNumber + "'";
                sqlCmdSlctTblInvoiceItem.CommandText = sqlCmdStrSlctTblInvoiceItem2;

                sqlTblInvoiceItemAdapter.SelectCommand = sqlCmdSlctTblInvoiceItem;
                sqlTblInvoiceItemAdapter.Fill(dtTblInvoiceItem);

                // Inspect tblInvoiceItem and print all SPSNumbers that have invoices against them 
                if (dtTblInvoiceItem != null)
                    if (dtTblInvoiceItem.Rows.Count > 0)
                    {
                        foreach (DataRow r in dtTblInvoiceItem.Rows)
                        {                                
                            // Write the report to the ExportFilePath
                            //WriteReport(strSpsNumber);

                            string ExportFilePath = @"\\testsrv\EXPORT\";  // locaiton where PDF reports will be written.
                            string ReportPath = @"/xxx/Report1";  //  Path to report on modabackupsql reportserver
                            string FileExtentionPDF = @".pdf";

                            PrintShippingDocLabelPDF.REService2005.ReportExecutionService _re;  //  proxy class for the report execution for 

                            // Report arguments 
                            string report = ReportPath;
                            string historyID = null;
                            string deviceInfo = null;
                            string format = @"PDF";
                            Byte[] results;
                            string encoding = String.Empty;
                            string mimeType = String.Empty;
                            string extension = String.Empty;
                            PrintShippingDocLabelPDF.REService2005.Warning[] warnings = null;
                            string[] streamIDs = null;
                            string strFilePath = ExportFilePath;  // location for writing PDF labels generated from executed reports
                            string strFileName;  // the name of pdf labels generated from executed reports

                            _re = new PrintShippingDocLabelPDF.REService2005.ReportExecutionService();
                            _re.Credentials = System.Net.CredentialCache.DefaultCredentials;

                            // Prepare Render arguments
                            PrintShippingDocLabelPDF.REService2005.ExecutionInfo ei = _re.LoadReport(report, historyID);
                            PrintShippingDocLabelPDF.REService2005.ParameterValue[] parameters = new PrintShippingDocLabelPDF.REService2005.ParameterValue[1];

                            // add the spsnumber as the report parameter
                            parameters[0] = new PrintShippingDocLabelPDF.REService2005.ParameterValue();
                            parameters[0].Name = "spsnumber";
                            parameters[0].Value = strSpsNumber;
                            strFileName = strSpsNumber + FileExtentionPDF;

                            // set the execution parameters
                            _re.SetExecutionParameters(parameters, "en-us");

                            // render the report
                            results = _re.Render(format, deviceInfo, out extension, out encoding, out mimeType, out warnings, out streamIDs);

                            // Create a file stream and write the report to it
                            using (FileStream stream = File.OpenWrite(strFilePath + strFileName))
                            {
                                stream.Write(results, 0, results.Length);
                            } // END using (FileStream stream = File.OpenWrite(StoredProcedures.strFilePath + StoredProcedures.strFileName))

                            // Set the IsValidatedByWms associated with SPSNumber to 1
                            // to indicate that the report has been printed.
                            //sqlConnTestDB.Open();

                            sqlCmdUpdateSlctPicking.CommandText = sqlCmdStrUpdate + "'" + strSpsNumber + "'";
                            sqlCmdUpdateSlctPicking.ExecuteNonQuery();

                            //sqlConnTestDB.Close();
                        }  // END foreach (DataRow r in dtTblInvoiceItem.Rows)

                        dtTblInvoiceItem.Clear();

                    } // if (dtTblInvoiceItem.Rows.Count > 0)

            }   //  END if (Convert.ToInt32(row["LabelPrintStatus"]) == 0)

        }  // END foreach (DataRow row in dtPicking.Rows)

    }  // END using (SqlConnection sqlConnTestDB = new SqlConnection("context connection=true"))
} // END public static void PrintShippingDocLabelsToPDF()

【问题讨论】:

  • 如果您尝试写入托管 SQL Server 的机器上的本地文件夹,这可以正常工作吗?
  • 嗨 Sven,代码在 CLR 之外工作(即它在控制台应用程序中工作,从那里我可以建立到服务器的连接运行我的查询并将报告写入文件系统,这一切都停止了当我将代码移动到 CLR 时工作)。共享实际上位于托管 SQL 的盒子上,因此,本质上它是一个本地文件夹。
  • 您使用什么权限注册了您的 CLR?您是否将您的数据库标记为 TRUSTWORTHY 并将您的程序集标记为 UNSAFE?如果不熟悉这些权限,请参阅MSDN
  • 错误不是来自您的代码,因为您没有加载文件,而是尝试保存文件。我猜这个错误来自ReportExecutionService。这是使用网络服务吗?
  • 我试图在 Visual Studio 2010 项目选项中使该过程不安全,但部署失败。我不知道如何将数据库标记为值得信赖的还没有看到这个。

标签: c# sql-server-2008-r2 sqlclr


【解决方案1】:

首先,关于这个问题的一些说明:

  • 无法加载动态生成的序列化程序集。

    序列化程序集用于通过 Web 服务进行通信

  • System.IO.FileLoadException

    此错误并非来自您的代码,因为您没有加载文件。您正在尝试保存文件。两者都要求尝试操作的程序集具有EXTERNAL_ACCESSPERMISSION_SET,但加载和保存不是一回事。

  • LoadFrom()、LoadFile()、Load(byte[]) 和 LoadModule() 已被主机禁用。

    这很可能是由于没有将程序集的 PERMISSION_SET 设置为 EXTERNAL_ACCESS。这需要将 DB 设置为 TRUSTWORTHY ON(不好),或者根据您签署程序集的密钥创建登录并授予该登录 EXTERNAL ACCESS ASSEMBLY 权限。

出于测试目的,您可以执行以下操作以查看它是否有效。将程序集部署为 SAFE 后,运行以下命令:

  • ALTER DATABASE [{db_name}] SET TRUSTWORTHY ON;
  • ALTER ASSEMBLY [{assembly_name}] WITH PERMISSION_SET = EXTERNAL_ACCESS;

然后尝试再次运行您的存储过程。你至少应该走得更远。但是,鉴于代码的当前形式,并且由于您说它已经作为控制台应用程序运行,我认为您最好保持这种方式并从 xp_cmdshell 运行它,或者如果尚未启用,请运行它来自 SQL 代理作业的命令步骤。

我认为这可以在 SQLCLR 中工作,但首先需要进行一些重组。

  • 不要在 .NET 代码中循环获取spsnumber。通过CURSOR 在T-SQL 中获取该列表,然后调用此过程并传入SPSNumber
  • 控制台应用程序(和 Windows 窗体)与 SQLCLR 之间的一个区别是控制台应用程序作为您的 Windows 登录名运行。但是,默认情况下 SQLCLR 作为 SQLSERVER NT 服务的登录帐户运行。该帐户需要对该文件夹的写入权限,或者您需要启用模拟以将安全上下文切换到运行存储过程的任何人,但这仅适用于 Windows 登录,而不适用于 SQL Server 登录。

一般说明:

  • 请不要将字符串连接到查询中,因为这会使您面临可能的 SQL 注入。相反,声明参数如下:

    SqlParameter _SpsNumber = new SqlParameter("@SpsNumber", SqlDbType.Int);
    sqlCmdUpdateSlctPicking.Parameters.Add(_SpsNumber);
    

    然后您将sqlCmdStrUpdate 更新为以@SpsNumber 结尾,而不是为每个项目更新sqlCmdUpdateSlctPicking.CommandText,您只需调用_SpsNumber.Value = strSpsNumber;(尽管如果它抱怨您可能需要执行Int32.Parse(strSpsNumber))。

  • 您不需要两个 if 语句:dtTblInvoiceItem != nulldtTblInvoiceItem.Rows.Count > 0。只需将它们组合成if((dtTblInvoiceItem != null) && (dtTblInvoiceItem.Rows.Count > 0))

【讨论】:

    猜你喜欢
    • 2019-06-24
    • 2015-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多