0

I get the following error when I attempt to execute a C# CLR stored procedure from SQL Server 2008 R2:

Msg 6522, Level 16, State 1, Procedure PrintShippingDocLabelsToPDF, Line 0
A .NET Framework error occurred during execution of user-defined routine or aggregate "PrintShippingDocLabelsToPDF":
System.InvalidOperationException: Cannot load dynamically generated serialization assembly. In some hosting environments assembly load functionality is restricted, consider using pre-generated serializer.

Please see inner exception for more information. --->

System.IO.FileLoadException: LoadFrom(), LoadFile(), Load(byte[]) and LoadModule() have been disabled by the host.
System.IO.FileLoadException:

It is my understanding that this is happening precisely because I am trying to write to a file on a network share. I have reviewed the following sources: https://msdn.microsoft.com/en-us/library/ms345106(v=sql.105).aspx, Accessing Sql FILESTREAM from within a CLR stored procedure, but I am still lost.

All I need to do is write several reports from SSRS to xxx.pdf files on a network share (where xxx represents a custom name). The code for this functionality is below and I am fairly certain that the below part is causing the error.

// 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))

Here is the full procedure. Can someone give me a pointer, please? I am working on a tight deadline. As a temporary solution, perhaps it is possible to write these reports to a temp table and then use a SSIS package to move them to a folder. If that is possible can someone give me a pointer on how I would store these files (varbinary(MAX these files are approximately 60KB-150KB in size)?

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()
Community
  • 1
  • 1
som
  • 1
  • 4
  • So this works without error if you try writing to a local folder on the box hosting SQL Server? – Sven Grosen Jul 22 '15 at 18:19
  • Hi Sven, the code works outside CLR (i.e. it works within a console application, from there I can establish a connection to to the server run my queries and write reports to the file system, all this stops working when I move the code to CLR). The share is actually located on the box hosting SQL so, essentially it is a local folder. – som Jul 22 '15 at 18:23
  • With what permissions did you register your CLR? Did you mark your database as TRUSTWORTHY and your assembly as UNSAFE? See [MSDN](https://msdn.microsoft.com/en-US/library/ms254956(v=vs.90).aspx) if unfamiliar with these permissions. – Sven Grosen Jul 22 '15 at 18:28
  • The error is not coming from your code since you are not loading a file, you are trying to save a file. I am guessing that the error is coming from `ReportExecutionService`. Is that using a web service? – Solomon Rutzky Jul 22 '15 at 18:34
  • I tried to make the procedure unsafe in visual studio 2010 project options but the deployment failed. I am not sure how to mark the DB as TRUSTWORTHY have not seen this yet. – som Jul 22 '15 at 18:37
  • Hi srutzky, it is using a the web service provided by our report server. But this same code works outside of the CLR, what leads you to believe that ReportExecutionService is the cause? I'm a bit lost here. – som Jul 22 '15 at 18:39
  • I said why I thought it was the cause: the error is happening with FileLoad and you are not loading a file. SSRS, however, is trying to load the report definition. And the serialization file has to do with web services. So, are you loading only your Assembly or are you also loading one for SSRS? I can tell you how to set TRUSTWORTHY via Visual Studio, though if you are only loading your assembly then it is a bad thing to do in Production. – Solomon Rutzky Jul 22 '15 at 18:40
  • Also, if you already have this running as a console app, then what is the benefit of using SQLCLR? Why not just call your console app in a CommandStep in a SQL Agent job? Then you don't need to mess with setting your Assembly to EXTERNAL_ACCESS, which is required. And that requires either the DB being set to TRUSTWORTHY (not good) or creating a login based on the key that you sign your assembly with and granting it `EXTERNAL ACCESS ASSEMBLY`. – Solomon Rutzky Jul 22 '15 at 18:48
  • Visual Studio is taking care of all the loading for me, so I am not sure how to answer your question. This is the first time that I'm doing something like this. What I can say, is that when I try to debug the project, I get passed the loading stage and go to an exception when I try to write the file. Makes sense? – som Jul 22 '15 at 18:50
  • For testing purposes, in Visual Studio, go to **Project Properties -> Project Settings -> Database Settings... -> Miscellaneous** and check the **Trustworthy** checkbox. Then in **Project Properties -> SQLCLR** set the **Permission level** drop-down to **EXTERNAL_ACCESS**. Did you have to add a reference to your project for an SSRS assembly? Such as `PrintShippingDocLabelPDF`? If so, _that_ assembly might need to be the one marked as **EXTERNAL_ACCESS**. – Solomon Rutzky Jul 22 '15 at 18:54
  • I did not have to add a reference to the project. But I also do not see Miscellaneous option in Database Settings. All I see is the permission level and when I try to specify External the deployment fails. – som Jul 22 '15 at 19:17
  • I am doing it this way based on something I read online. How can I do it differently? – som Jul 22 '15 at 19:41
  • **Miscellaneous** is one of three tabs in the **Database settings** pop-up. I am using VS 2012, and I also have 2013 and it should be the same there as well. Hopefully you have updated your SSDT ( https://msdn.microsoft.com/en-us/library/mt204009.aspx ). Regardless of how you set that option, you can't call a Web service from a `SAFE` assembly. And I still don't understand why you are doing this via SQLCLR. – Solomon Rutzky Jul 22 '15 at 20:06
  • Regarding "I am doing it this way based on something I read online." Do you have any idea how much horribly incorrect advice is out there? You need to be very careful of what advice you follow. What is the link for this article? And I already said, if you can do this in a console app, either call via `xp_cmdshell` or via a Command step of a SQL Agent job. – Solomon Rutzky Jul 22 '15 at 20:08
  • The problem might actually be coming from the `_re.Render()` method call, since that probably does all of the work. I think this could work in SQLCLR with a little bit of restructuring. Don't do the loop to get `spsnumber` in this code. Get that list in T-SQL via a CURSOR, then call this proc passing in `SPSNumber`. And you should be using parameterized queries instead of concatenating the `spsnumber` into the query. One diff between console app and SQLCLR is that console app runs as you, SQLCLR runs as the Logon Account for the SQLSERVER service. That account needs write access to the folder. – Solomon Rutzky Jul 23 '15 at 01:40
  • Hey srutzky, thank you for the advice. After trying to get this to work via SQLCLR, ended up going back to my regular console app and now have some sort of solution in place. Part of the idea came form here: http://www.codeproject.com/Articles/15555/Generating-PDF-reports-programmatically-using-SQL. But now, I can't seem to find the CLR part. I'll also add parameterized queries, thanks! – som Jul 23 '15 at 12:22
  • Great. I posted an answer with most of the above info. I looked at that link and yeah, it is just about a regular console app or Windows form, not SQLCLR. That does not mean that this couldn't be made to work in SQLCLR, but there is stuff that needs to be done that is not part of that article. – Solomon Rutzky Jul 23 '15 at 14:32
  • @som If my answer helped solve your problem, please don't forget to mark it as "accepted" (the check-mark next to the answer). Thanks :). – Solomon Rutzky Aug 10 '15 at 02:55
  • This KB article was helpful for me https://support.microsoft.com/en-nz/help/913668/error-message-when-you-use-a-common-language-runtime-object-in-sql-ser – K Scandrett Apr 11 '19 at 00:43

1 Answers1

0

First, some notes about the issue:

  • Cannot load dynamically generated serialization assembly.

    The serialization assembly is used for communicating over a Web Service

  • System.IO.FileLoadException

    This error is not coming from your code as you are not loading a file. You are trying to save a file. Both require the assembly attempting the operation to have a PERMISSION_SET of EXTERNAL_ACCESS, but loading and saving are not the same thing.

  • LoadFrom(), LoadFile(), Load(byte[]) and LoadModule() have been disabled by the host.

    This is most likely due to not having the assembly's PERMISSION_SET set to EXTERNAL_ACCESS. That requires either the DB being set to TRUSTWORTHY ON (not good) or creating a Login based on the key that you sign your assembly with and granting that Login the EXTERNAL ACCESS ASSEMBLY permission.

For testing purposes, you can do the following just to see if it will work. After you deploy your assembly as SAFE, run the following:

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

And try to run your stored procedure again. You should at least get farther. However, given the code's current form, and since you say that it already works as a Console App, I think you are better off keeping it that way and running it either from xp_cmdshell, or if that hasn't been enabled, run it from a Command step of a SQL Agent job.

I think this could work in SQLCLR, but it needs a little bit of restructuring first.

  • Don't do the loop to get spsnumber in the .NET code. Get that list in T-SQL via a CURSOR, and then call this proc passing in SPSNumber.
  • One difference between console apps (and Windows Forms) and SQLCLR is that console apps run as your Windows Login. SQLCLR, however, by default runs as the Logon Account for the SQLSERVER NT service. Either that account needs write access to the folder, or you need to enable Impersonation to switch the security context to whoever is running the stored procedure, but that only works for Windows Logins, not SQL Server Logins.

General notes:

  • Please do not concatenate string into a query as that opens you up to possible SQL Injection. Instead, declare the parameters as follows:

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

    and then you update sqlCmdStrUpdate to end with @SpsNumber, and instead of updating sqlCmdUpdateSlctPicking.CommandText for each item, you just call _SpsNumber.Value = strSpsNumber; (though you might need to do Int32.Parse(strSpsNumber) if it complains).

  • You don't need two if statements for: dtTblInvoiceItem != null and dtTblInvoiceItem.Rows.Count > 0. Just combine them into if((dtTblInvoiceItem != null) && (dtTblInvoiceItem.Rows.Count > 0))

Solomon Rutzky
  • 46,688
  • 9
  • 128
  • 171