Thursday, January 29, 2015

CRM LINQ returns NULL values

I faced this issue recently. The exact replication steps would be to initialize a CRM Service Context and use it to retrieve the same (set of) record(s) more than once, with first query retrieving a set of attributes and second query retrieving some other set of attributes. In this scenario, you would notice that the second query returns NULL values for almost all the attributes.

The reason for this behavior that CRM Service Context caches the results once retrieved. So when the first query returned a set of attributes, it cached the record with only those values. When the second query is executed, the query is not run on the DB at all, but on the cached data. Since the second query has a different set of attributes, it returns NULL values.

How to workaround this issue:

There are two options.

1. Re-create the service context everytime a query is executed. This makes the ServiceContext fetch the data from CRM everytime.

2. Or you could call the ClearChanges() method in the Service context. But just take care, that you don't clear the CRUD changes.

Tuesday, January 20, 2015

Exporting a CRM SSRS Report from .NET Code

We recently came across a requirement where I had to export some custom SSRS Report to Excel file on a schedule and store the file in a file system path. This will contain some parameters to be passed and I had to pass some dynamics values to these parameters. Hence report snapshot is not an option here. 

One option was to go with a console application which can be scheduled using Windows Task Scheduler and offers much more flexibility over the functionality. On searching around internet, I didn't find much help. The solution was scattered across many blog posts and forums. Consolidating the solution we implemented here. 

The steps involve

  1. Retrieving the report server URL from deployment properties using deployment service (But wasn't required for my case. So not including the code for that. I retrieved the report server URL from app.config)
  2. Retrieving current user ID and organization ID, using a WhoAmIRequest.
  3. Retrieving the report details from CRM (using report name probably). The report record in CRM contains the report name on SSRS, it will basically be GUID.
  4. Initialize a report viewer control (.NET Control) pointing to the above report.
  5. Save the report as Excel.
Posting a sample code. May not work as is, since I am just posting snippets of my production code. Please add reference to Microsoft.ReportViewer.WinForms assembly for ReportViewer control.
string reportServerUrl = "http://xrm.com"

WhoAmIRequest whoami = new WhoAmIRequest();
WhoAmIResponse iam = (WhoAmIResponse)orgService.Execute(whoami);
Organization orgObject = orgService.Retrieve(Organization.EntityLogicalName, iam.OrganizationId, new Microsoft.Xrm.Sdk.Query.ColumnSet(true)).ToEntity();

Guid? reportId = null;
using (Xrm serviceContext = new Xrm(orgService))
{
      reportId = (from r in serviceContext.ReportSet
                  where r.Name == reportNode.Attribute(XName.Get(nameAttribute)).Value
                  select r.ReportId).FirstOrDefault();
}

ReportViewer reportViewer = new ReportViewer();
reportViewer.ServerReport.ReportPath = @"/" + orgObject.Name + "_MSCRM/CustomReports/{" + reportId.ToString().ToLower() + "}";
reportViewer.ServerReport.ReportServerUrl = new Uri(reportServerUrl);
reportViewer.ProcessingMode = ProcessingMode.Remote;
reportViewer.ServerReport.Timeout = 1200000;
reportViewer.ServerReport.ReportServerCredentials.NetworkCredentials = credentials;
ReportDataSourceInfoCollection reportDataSources = reportViewer.ServerReport.GetDataSources();

DataSourceCredentials creds = new DataSourceCredentials();
creds.UserId = iam.UserId.ToString();
creds.Password = iam.OrganizationId.ToString();
creds.Name = reportDataSources[0].Name;

DataSourceCredentialsCollection credsCollection = new DataSourceCredentialsCollection();
credsCollection.Add(creds);

reportViewer.ServerReport.SetDataSourceCredentials(credsCollection);
reportViewer.ServerReport.SetParameters(new ReportParameter("statuscode", "1"));
reportViewer.ShowParameterPrompts = false;

string mimeType, encoding, extension;
string[] streams;
Warning[] warnings;
byte[] fileBytes = reportViewer.ServerReport.Render("Excel", string.Empty, out mimeType,
                            out encoding, out extension, out streams, out warnings);

string outputPath = reportNode.Attribute(XName.Get("OutputFilePath")).Value + "\\" + outputFileName;
using (FileStream fs = new FileStream(outputPath, FileMode.Create))
{
    fs.Write(fileBytes, 0, fileBytes.Length);
}