none
Slow Performance with Dynamic Grouping and ReportViewer In Local Mode

    Question

  • This topic refers to RS 2008 as used with VS 2010 applications although the remote mode was tested using RS 2008 R2 as the Report Server -

    I'm having quite a problem with very poor performance in RS 2008. This is definately not a data related problem. In order to find the bottle neck I have created two reports with one grouping. One has a normal static grouping expression of Fields!MyFieldName.Value while the other also has a static expression but of the form Fields("MyFieldName").Value. This because I need to use dynamic grouping and will replace "MyFieldName" with Parameters!MyGroupParam.Value when this problem is resolved. I am pulling 10k records into the DataSet with about 200 distinct values in MyFieldName.

    When I load these reports as rdl files to the report server and either run them from the Report Manager OR use a WebViewer or WinForm Viewer in REMOTE mode there is no problem and both reports render in about the same time (about 1 to 2 seconds).

    When I use the same report definitions as rdlc (Local Mode) then the first report still renders fast (about 1 to 3 seconds).  HOWEVER the second report requires at least 8 times longer! This is the report with Fields("MyFieldName").Value as the grouping expression. This means a rendering time of about 8 seconds in WinForms Viewer and a whopping 18 seconds in the WebForm Viewer.  To add insult to injury, each additional "dynamic" (it isn't really dynamic yet is it) grouping parameter adds the same time again.  This means with 4 "dynamic" parameters the rendering time in the Web Viewer is over a minute. The behaviour is constant even with
    considerably fewer records (down to about 1k).

    Unfortunately I can not use remote mode (report server) because I need to load my data out of a business logic layer in my application. With this poor performance there is no way that I can use Reporting Services to meet our goals. At any rate this must be a bad bug in the local processing since all that is really happening is a quick parse of the expression after which both syntaxes should behave the same.

    The use of a "dynamic" expression in the sort expression or anywhere else does not seem to cause any problems. It is only the group expression that can't stand it.

    I would be glad to hear from someone whether there is a work around.  It doesn't sound reasonable for a product that has been in use for so many years now to have this sort of a problem.

    I have filed a connect bug at -
    https://connect.microsoft.com/VisualStudio/feedback/details/561679/reporting-services-using-parameter-for-dynamic-grouping-very-slow

    and have been conversing with Teo Lachev on the matter. He suggested posting here.

    Be glad for any insight or help.

    Alle

    Thursday, June 03, 2010 9:10 PM

All replies

  • I'm going to bump this with the addition of a link to another thread that apparently is the same problem. Obviously there is something wrong in the local processing of the report. Possibly this has started with the release of VS 2010 (Framework 4.0) and wasn't caught in the beta testing?

    The other thread is - ReportViewer local report 10 times slower than same report from Report Builder 2.0

    Alle

    Sunday, June 06, 2010 9:44 PM
  • After testing some more, I can now confirm that this problem only occurs with the framework 4.0.

    After setting the target framework to 3.5 the delivery time is now OK. That is of course NOT a solution.

    Alle

    Monday, June 07, 2010 12:18 PM
  • Well in the past weeks I have been in contact with the Microsoft developer team regarding this problem. It seems there is "kind of good news" and not so good news. The problem seems to be a degression that occured with the transition of the report viewer local mode to Net 4.0 running in the sandbox Domain (the Sandbox Domain is used by default in Net 4.0). There is a good blog post from Brian Hartmann describing the reasoning and importance behind using the sandbox domain (for example to avoid memory leaks when running the report viewer in an aspnet page). Expression Evaluation in Local Mode Unfortunately this problem was not caught in the Beta Testing.

    The "not so good" news is that the problem seems to be related to expression host (in the sandbox domain) calls into the application domain (IOW cross domain calls) which at least in the case of the grouping expression are apparently not handled very effeciently resulting in extremely long execution times that are in fact cumulative. (For each grouping expression that is considered dynamic the penalty occurs. 5 dynamic groupings = 5 penalties). It could be that other scenarios, other than dynamic grouping, also cause this behavior. Extreme means about 15 seconds per grouping parameter in ASPNET or about 7 seconds per grouping paramter in a WinForms app.

    Since this area (cross domain calls) is a very sensitive area that also seems to be tightly tied into the CAS security changes in Net 4.0, it doesn't seem to be possible to make a quick "hot fix" or so to resolve the problem. In fact, it may well be possible that the problem can only adressed in a major service pack or new release. The bug is filed and the team is aware of the problem.

    The "kind of good" news is that for many scenarios it is possible to work around this problem. However, be aware that going back to the FW 3.5 or using any of the following methods other than the replacement of the dynamic expressions, will result in the report viewer being run in the current AppDomain. This in turn means that you will have a memory leak for each report that is executed until this report or the AppDomain is unloaded. In the case of an ASPNET application this could well be the session timeout for each report and can quickly become a problem. For a WinForms application this should not be a problem.

    Workarounds:

    You can compile your application in Net 3.5 and avoid all performance problems with the Report Viewer (which is multi- targeted for 3.5 and 4.0). If you do so, DO NOT use the viewer option "ExecuteReportInSandboxDomain" since that will bring back the problem (duh). You are then running in the current AppDomain, could have memory leak problems with an ASPNET application, and of course abandon any use of other Net 4.0 features.  Loading the old viewer (can be done) does allow the use of the sandbox domain but would possibly bring back the problem of the sandbox domain not unloading properly. It's a no win situation I think.

    Alternatively you can stay with Net 4.0 but force the use of´ legacy CAS security.
    Disclaimer - I do not know what other ramifications this could have with other controls or methods in your application. Use at your own risk! Although this was suggested by MS as a possible workaround you are setting application level security policies with these tags!

    • For a WinForms application you can use <NetFx40_LegacySecurityPolicy enabled="true" /> in the configuration -> runtime section of the app.config file which forces the usage of the current AppDomain. Although the MS documentation for this property does not indicate it, this tag only has an effect in WinForms applications.
    • In an ASP Net application you can use <trust legacyCasModel="true" level="Full"/>  in the system.web section of the web.config file to achieve the same result.

    The last method that I am aware of  is to replace the parts of the report definition that result in the extremely poor performance at runtime. This includes at least the use of parameters in the grouping expression and could also be caused by other elements. This keeps the report performance, still uses Net 4.0 and keeps the report in the sandbox domain with all of it's advantages (including the fixed issue of the memory leak). This is actually easier than one might think and actually outperforms dynamic grouping in the current AppDomain! Since this post is already very long,  I will post this separately.

    HTH
    Alle

    Monday, July 05, 2010 3:41 PM
  • Replacing Dynamic Grouping at Runtime

    Since the release of Visual Studio 2010 and Net 4.0 there has unfortunately been a major loss of performance when dynamic grouping is used in a report loaded into a report viewer running in local mode in the sandbox Domain (which is default in Net 4.0). This is a blocking bug which is known at Microsoft but probably will not be resolved in the near future (see my previous post).

    In order to still use Net 4.0 in the standard configuration and use the current version of the report viewer in local mode it is possible to load the report definition at run time, after having replaced the dynamic grouping expressions with static expressions. At the moment this seems to be the easiest with XLinq. Since I assume most of you are not necessarily more experienced with XLinq then I am I have included an example of  how this can be done. Any better suggestions are appreciated.

    I would suggest, that one continues to design the reports as originally intended using parameters since then when the problem is fixed it would only be necessary to comment out the replacement code. By doing this and assigning the parameters in the code behind / beside you also open up the possibility of very generic processing of the repacement process since the named parameter usage in the report definition can easily be determined using a similar XLinq process. Again, I will try to cover this in a separate post when I have time.

    Sample replacement of the dynamic grouping parameter at runtime in code behind:

        //this is the loading of the report definition in XLing and changing the value of "Group1"
        XElement report = XElement.Load(Server.MapPath("/") + "Reports/" + "MyReport.rdl");
        report = ChangeXMLGroupingValues(report, "Group1", "ModifiedDate", null); //see below
    
        //report has now been changed and the dynamic expression has been replaced
        //we now need to load the report definition in the ReportViewer.LocalReport
        using (System.IO.MemoryStream str = new System.IO.MemoryStream()){
           report.Save(str);
           str.Position = 0;
           using (System.IO.StreamReader read = new System.IO.StreamReader(str)){
             ReportViewer1.LocalReport.LoadReportDefinition(read);
           }
        }
    
        //create a DataSource and add it
        Microsoft.Reporting.WebForms.ReportDataSource rds = new Microsoft.Reporting.WebForms.ReportDataSource("DataSet1", dt);
        ReportViewer1.LocalReport.DataSources.Clear();
        ReportViewer1.LocalReport.DataSources.Add(rds);
    
    	//Add Parameters to the local report here - must be after the report definition is loaded
    
        //refresh / run the report
        ReportViewer1.LocalReport.Refresh();
    The definition of the function ChangeXMLGroupingValues(....) :
        protected XElement ChangeXMLGroupingValues(XElement reportXML, string strGroupName, string strGroupBy, string strSortBy) {
          string dns = "{" + reportXML.GetDefaultNamespace().ToString() + "}";
          
          var Result =
            from el in reportXML.Descendants()
            where el.Name == (dns + "Group") && (string)el.Attribute("Name") == strGroupName
            select el;
    
          if (Result.Count() > 0) {
            XElement MyElement = Result.First();
            MyElement.Element(dns + "GroupExpressions").Element(dns + "GroupExpression").Value = "=Fields!" + strGroupBy + ".Value";
            if (!string.IsNullOrEmpty(strSortBy)) {
              MyElement.Parent.Element(dns + "SortExpressions").Element(dns + "SortExpression").Element(dns + "Value").Value = "=Fields!" + strSortBy + ".Value";
            }
            else{
              //use the Group Expression as Sort Expression
              MyElement.Parent.Element(dns + "SortExpressions").Element(dns + "SortExpression").Element(dns + "Value").Value = "=Fields!" + strGroupBy + ".Value";
            }
          }
          return reportXML;
        }
    

    HTH
    Alle

    Monday, July 05, 2010 3:45 PM
  • Hi there.

    I experienced the same problem in .NET 4.5 in ASP.NET based application with heavy use of dynamic types for serialization and deserialization.  So I could not use <trust legacyCasModel="true" level="Full"/>, 

    I managed to create another workaround based on custom CAS security settings in separated AppDomain directly in original application without the need to deploy reports to RS, or to create some stand alone application/web service based on .NET3.5 or settings mentioned in previous replies.

    My reports went down from 18s to 2s. Maybe this can be further improved by not disposing and re-initializing LocalReport every time - but that way I would have to handle multi-threaded access and maybe some pooling...

    Because of new AppDomain, I had to deal with cross domain communication. I tried to avoid Marshalling as much as possible, so I used the simplest solution => serialized JSON objects. Although this might impact performance a little bit. I implemented a very simple AppDomain recycle policy just to contain memory leaks.

    Solution is based on this code snippet.

    var setup = new AppDomainSetup
                        {
                            ApplicationBase = Environment.CurrentDirectory,
                            LoaderOptimization = LoaderOptimization.MultiDomainHost
                        };
                        setup.SetCompatibilitySwitches(new[] { "NetFx40_LegacySecurityPolicy" });
                        _casPolicyEnabledDomain = AppDomain.CreateDomain(_appDomainName, null, setup);


    My whole solution along with some infrastructure data looks like this:

    RdlcInvoker.cs

    using System;
    using Common.DataStructures;
    
    namespace Ada.Reports
    {
        public class CleanAppDomainEventArgs : EventArgs
        {
            #region Properties
            public int RunCountFromLastUnload { get; set; }
            public DateTime? LasUnloadTime { get; set; }
            #endregion
    
            #region Methods
            public CleanAppDomainEventArgs(int runCountFromLastUnload, DateTime? lasUnloadTime)
            {
                RunCountFromLastUnload = runCountFromLastUnload;
                LasUnloadTime = lasUnloadTime;
            }
            #endregion
        }
    
        public class RdlcInvoker : DisposableManagedBase
        {
            #region Fields
            private AppDomain _casPolicyEnabledDomain;
            private readonly object _syncRoot = new object();
            private readonly Func<CleanAppDomainEventArgs, bool> _shouldResetAppDomain;
            private readonly string _appDomainName;
            private int _runCountFromLastUnload = 0;
            private DateTime _lasUnloadTime = DateTime.MinValue;
            private Proxy _proxy;
            #endregion
    
            #region Methods
            public RdlcInvoker(Func<CleanAppDomainEventArgs, bool> shouldResetAppDomain = null, string appDomainName = "CAS Policy Enabled Domain")
            {
                if (shouldResetAppDomain == null) shouldResetAppDomain = AlwaysReCreateShouldResetAppDomain;
    
                _shouldResetAppDomain = shouldResetAppDomain;
                _appDomainName = appDomainName;
            }
    
            public byte[] Render<T, TArgs>(TArgs args) where T : RdlcReportWrapperBase, new()
            {
                var casPolicyEnabledDomain = GetOrCreateCasSecurityDomain();
    
                _proxy = casPolicyEnabledDomain.CreateInstanceAndUnwrap<Proxy>();
                var ret = _proxy.Render<T, TArgs>(args);
                return ret;
            }
    
            private AppDomain GetOrCreateCasSecurityDomain()
            {
                lock (_syncRoot)
                {
    
                    if (_casPolicyEnabledDomain != null && _shouldResetAppDomain(new CleanAppDomainEventArgs(_runCountFromLastUnload, _lasUnloadTime)))
                    {
                        UnloadAppDomain();
                    }
                    else
                    {
                        _runCountFromLastUnload++;
                    }
                    if (_casPolicyEnabledDomain == null)
                    {
                        var setup = new AppDomainSetup
                        {
                            ApplicationBase = Environment.CurrentDirectory,
                            LoaderOptimization = LoaderOptimization.MultiDomainHost
                        };
                        setup.SetCompatibilitySwitches(new[] { "NetFx40_LegacySecurityPolicy" });
                        _casPolicyEnabledDomain = AppDomain.CreateDomain(_appDomainName, null, setup);
                    }
    
                    return _casPolicyEnabledDomain;
                }
            }
    
            private bool AlwaysReCreateShouldResetAppDomain(CleanAppDomainEventArgs args)
            {
                return true;
            }
    
            protected override void DisposeManaged()
            {
                if (_casPolicyEnabledDomain != null) UnloadAppDomain();
            }
    
            private void UnloadAppDomain()
            {
                _proxy = null;
                AppDomain.Unload(_casPolicyEnabledDomain);
                _casPolicyEnabledDomain = null;
                _lasUnloadTime = DateTime.Now;
                _runCountFromLastUnload = 0;
            }
            #endregion
        }
    }


    RdlcReportWrapperBase.cs

    namespace NS {

    public abstract class RdlcReportWrapperBase { /// <summary> /// When implemented in a derived class, gets the path to RDLC file, which is loaded from disk each time a report is executed. /// </summary> /// <returns>Fully qualified path to RDLC file</returns> public abstract string StatementRdlcPath { get; } /// <summary> /// Renders report from given JSON data. JSON data have to be deserialized by report processor. /// </summary> /// <param name="jsonData">JSON object repreenting the data. This is a way to avoid/simplify marshaling in case we already have a JSON object and we know how to deserialize it. Value can not be <c>null</c>.</param> /// <returns>Byte array representing the rendered content of report.</returns> public abstract byte[] RenderFromStringJson(string jsonData); }

    }

    ProxyBase.cs

    using System;
    using System.Reflection;

    namespace NS {

    public class ProxyBase : MarshalByRefObject { protected Assembly GetAssembly(string assemblyPath) { try { return Assembly.LoadFile(assemblyPath); } catch (Exception) { return null; // throw new InvalidOperationException(ex); } } }

    }


    Proxy.cs

    using System;

    namespace NS {

    public class Proxy : ProxyBase { public byte[] Render<T, TArgs>(TArgs args) where T : RdlcReportWrapperBase, new() { var sris = Newtonsoft.Json.JsonConvert.SerializeObject(args); var instance = Activator.CreateInstance<T>(); var buffer = instance.RenderFromStringJson(sris); return buffer; } }

    }


    AppDomainExtensions.cs

    using System;

    namespace NS {

    public static class AppDomainExtensions { public static T CreateInstanceAndUnwrap<T>(this AppDomain appDomain) where T : MarshalByRefObject { var type = typeof(T); var value = (T)appDomain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName); return value; } }

    }

    ReportItem.cs (DATA CLASS - depents on project and business data passed to report)

    using System;

    namespace NS {

    [Serializable] public class ReportItem { ...... }

    }


    MyCustomReportWrapper.cs - this would wrap Your logic, which passes data to LocalReport.

    using System.Collections.Generic;
    using System.IO;
    using Microsoft.Reporting.WebForms;

    namespace NS {

    public class MyCustomReportWrapper : RdlcReportWrapperBase { public override string StatementRdlcPath { get { return "YourPath goes here - be carefull - this is new AppDomain => You would not have access to HttpContext etc. for path resolution => need to pass the path from outside world"; } } public override byte[] RenderFromStringJson(string jsonData) { var data = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ReportItem>>(jsonData); var ms = RenderReport(this.StatementRdlcPath, ris); return ms.ToArray(); } public static MemoryStream RenderReport(string reportPath, List<ReportItem> reportItems) { var ms = new MemoryStream(); using (var fs = File.OpenRead(reportPath)) { using (var r = new LocalReport()) { r.LoadReportDefinition(fs); var ds = new ReportDataSource("dsItems"); ds.Value = reportItems; r.DataSources.Add(ds); Warning[] warnings; string[] streamids; string mimeType; string encoding; string filenameExtension; var bytes = r.Render("PDF", null, out mimeType, out encoding, out filenameExtension, out streamids, out warnings); ms.Write(bytes, 0, bytes.Length); ms.Seek(0, SeekOrigin.Begin); } } return ms; } }


    DisposableBase.cs

    using System;
    
    namespace Common.DataStructures {
    	/// <summary>
    	/// Represents the basic disposable implementation.
    	/// Use this class as the reference/base class for all classes, that need to be safely disposable and does not inherit from another class.
    	/// </summary>
    	public abstract class DisposableBase : IDisposable {
    		#region Fields
    		private bool _disposed;
    		#endregion
    
    		#region Properties
    		/// <summary>
    		/// Gets the value indicating, whether the instance has already been disposed.
    		/// </summary>
    		public bool IsDisposed { get { return _disposed; } }
    		#endregion
    
    		#region Methods
    		/// <summary>
    		/// Destroys current instance disposing all resources.
    		/// </summary>
    		~DisposableBase() {
    			Dispose(false);
    		}
    		/// <summary>
    		/// Disposes current instance.
    		/// </summary>
    		public void Dispose() {
    			Dispose(true);
    			GC.SuppressFinalize(this);
    		}
    		/// <summary>
    		/// Disposes current instance.
    		/// </summary>
    		/// <param name="disposing">Specifies, whether the method was called from <see cref="Dispose()"/> or from the finalizer. True represents the call from <see cref="Dispose()"/> method.</param>
    		public void Dispose(bool disposing) {
    			if (_disposed) return;
    			try {
    				if (disposing) {
    					DisposeManaged();
    				}
    				DisposeUnmanaged();
    			}
    			finally {
    				_disposed = true;
    			}
    		}
    		/// <summary>
    		/// WHen implemented in a derived class, should dispose unmanaged resource.
    		/// </summary>
    		protected abstract void DisposeUnmanaged();
    		/// <summary>
    		/// When implemented in a derived class, should dispose managed resources.
    		/// </summary>
    		protected abstract void DisposeManaged();
    		#endregion
    	}
    	/// <summary>
    	/// Represents the basic class implementing disposable pattern, which deals only with managed resources.
    	/// This class provides basic implementation of <see cref="DisposeUnmanaged"/> method (because there aren't any - does nothing)
    	/// Use this class as the base class for all classes, which need to safely dispose only managed resources and does not derive from other class.
    	/// </summary>
    	public abstract class DisposableManagedBase : DisposableBase {
    		protected override void DisposeUnmanaged() { }
    	}
    }
    }

    Thursday, October 16, 2014 3:48 AM
  • Hi...can you put your code in zip file and share...I am facing the same issue...My report takes 35 seconds for 3000 rows of data...incredibly slow...Thanks.
    Monday, October 20, 2014 6:05 PM
  • Also my email address is thakkarbhav@Hotmail.com if you wish to send the zip file...Appreciate your time and effort.

    Best,

    Bhavesh

    Monday, October 20, 2014 7:50 PM
  • Hello,

    Could you please send your solution to my mail: rohit.rokade@gmail.com


    Rohit Rokade

    Thursday, November 20, 2014 12:01 PM
  • i know its a old post but there is no fix yet for the ReportViewer and someone might still need help, so based on the code from Vladimir Fedorco I created this method that all it does is take the current reportViewer parameters and copy to other running in a separated domain so it requires very little changes in your current code:

    private static byte[] Render(string reportRenderFormat, string deviceInfo, string DisplayName, string ReportPath, bool Visible, ReportDataSourceCollection DataSources, string repMainContent, List<string[]> repSubContent, ReportParameter[] reportParam)
    {
        AppDomainSetup setup = new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory, LoaderOptimization = LoaderOptimization.MultiDomainHost };
        setup.SetCompatibilitySwitches(new[] { "NetFx40_LegacySecurityPolicy" });
        AppDomain _casPolicyEnabledDomain = AppDomain.CreateDomain("Full Trust", null, setup);
        try
        {
            WebReportviewer.FullTrustReportviewer rvNextgenReport2 = (WebReportviewer.FullTrustReportviewer)_casPolicyEnabledDomain.CreateInstanceFromAndUnwrap(typeof(WebReportviewer.FullTrustReportviewer).Assembly.CodeBase, typeof(WebReportviewer.FullTrustReportviewer).FullName);
            rvNextgenReport2.Initialize(DisplayName, ReportPath, Visible, reportParam, reportRenderFormat, deviceInfo, repMainContent, repSubContent);
                
            foreach (ReportDataSource _ReportDataSource in DataSources)
            {
                rvNextgenReport2.AddDataSources(_ReportDataSource.Name, (DataTable)_ReportDataSource.Value);
            }
               
            return rvNextgenReport2.Render(reportRenderFormat, deviceInfo);
        }
        finally
        {
            AppDomain.Unload(_casPolicyEnabledDomain);
        }
    }

    As you would expect, the new assembly WebReportviewer.FullTrustReportviewer
    all it does is just run the report. that's it. here is the code, it should be in a separated project:

    namespace WebReportviewer
    {
        [Serializable]
        public class FullTrustReportviewer : MarshalByRefObject
        {
            private ReportViewer FullTrust;        
            public FullTrustReportviewer() 
            {
                FullTrust = new ReportViewer();
                FullTrust.ShowExportControls = false;
                FullTrust.ShowPrintButton = true;
                FullTrust.ShowZoomControl = true;
                FullTrust.SizeToReportContent = false;
                FullTrust.ShowReportBody = true;
                FullTrust.ShowDocumentMapButton = false;
                FullTrust.ShowFindControls = true;
               FullTrust.LocalReport.SubreportProcessing += LocalReport_SubreportProcessing;
               //FullTrust.LocalReport.SetBasePermissionsForSandboxAppDomain(new PermissionSet(PermissionState.Unrestricted));
            }
    
            public void Initialize(string DisplayName, string ReportPath, bool Visible, ReportParameter[] reportParam, string reportRenderFormat, string deviceInfo, string repMainContent, List<string[]> repSubContent)
            {
                FullTrust.LocalReport.DisplayName = DisplayName;
                FullTrust.LocalReport.ReportPath = ReportPath;
                FullTrust.Visible = Visible;
                FullTrust.LocalReport.LoadReportDefinition(new StringReader(repMainContent)); 
                FullTrust.LocalReport.SetParameters(reportParam);
                
                repSubContent.ForEach(x =>
                {
                    FullTrust.LocalReport.LoadSubreportDefinition(x[0], new StringReader(x[1]));
                });
                FullTrust.LocalReport.DataSources.Clear();
            }       
    
            public byte[] Render(string reportRenderFormat, string deviceInfo)
            {
                return FullTrust.LocalReport.Render(reportRenderFormat, deviceInfo);
            }
            public void AddDataSources(string p, DataTable datatable)
            {
                FullTrust.LocalReport.DataSources.Add(new ReportDataSource(p, datatable));
            }
    
            public SubreportProcessingEventHandler SubreportProcessing { get; set; }
    
            public static void LocalReport_SubreportProcessing(object sender, SubreportProcessingEventArgs e)
            {
                LocalReport lr = (LocalReport)sender;
    
                e.DataSources.Clear();
                ReportDataSource rds;
    
                if (e.ReportPath.Contains("DataTable2"))
                {
                    DataTable dt = (DataTable)lr.DataSources["DataTable2"].Value;
                    DataView dv = new DataView(dt);
                    dv.RowFilter = string.Format("Id={0}", e.Parameters["Id"].Values[0]);
    				rds = new ReportDataSource("DataTable2", dv.ToTable());
                    e.DataSources.Add(rds);
                }
            }
        }
    }
    hope this is helpful for someone. regards.
    • Edited by Pepepaco Monday, June 08, 2015 7:56 PM edited to add improved code.
    Thursday, June 04, 2015 10:37 PM
  • Thank you so very much Pepepaco and Vladimir.
    Wednesday, August 26, 2015 3:23 PM
  • I use only LocalReport for generate (render) a RDLC file in Excel-PDF formats, without ReportViewer object. Any workaround about it?

    www.kiquenet.com/profesional

    Thursday, November 12, 2015 10:50 PM
  • Adrian Nichols has a helper code in

    https://github.com/AdrianNichols/ssrs-non-native-functions/blob/17ee83c9988acc638eb11f961caf0b2a6b77b555/SSRS_Demo/Business/reportHelper.cs

    Keys: RenderReportToMemoryAsPDFInAnotherAppDomain Method and ReportHelperInAppDomain  class

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Microsoft.Reporting.WebForms;
    using System.Xml;
    using System.Reflection;
    using System.Diagnostics;
    using System.Text;
    using System.IO;
    using System.Data;
    
    namespace Demonstrations.Business
    {
        public static class reportHelper
        {
            private static string GetDeviceInfo(string uiCulture, string format = "pdf", string marginscsv = "0,0,0,0", string orientation = "portrait")
            {
                string deviceInfo;
                string[] margins = marginscsv.Split(',');
    
                string tm = margins[0]; // Top margin
                string lm = margins[1]; // Left margin
                string bm = margins[2]; // Bottom margin
                string rm = margins[3]; // Right margin
    
                if (uiCulture == "en-US")
                {
                    deviceInfo = (orientation == "portrait") ? "<PageWidth>8.5in</PageWidth><PageHeight>11in</PageHeight>" : "<PageWidth>11in</PageWidth><PageHeight>8.5in</PageHeight>";
                }
                else
                {
                    deviceInfo = (orientation == "portrait") ? "<PageWidth>21cm</PageWidth><PageHeight>29.7cm</PageHeight>" : "<PageWidth>29.7cm</PageWidth><PageHeight>21cm</PageHeight>";
                }
    
                deviceInfo = "<DeviceInfo>" + "<OutputFormat>" + format + "</OutputFormat>" + deviceInfo + "  <MarginTop>" + tm + "cm</MarginTop>" + "  <MarginLeft>" + lm + "cm</MarginLeft>" + "  <MarginRight>" + rm + "cm</MarginRight>" + "  <MarginBottom>" + bm + "cm</MarginBottom>" + "</DeviceInfo>";
    
                return deviceInfo;
            }
    
            public static string GetDeviceInfoFromReport(LocalReport rpt, string uiCulture, string format)
            {
                string strMargins;
                string orientation;
    
                //Render overwrites margins defined in RDLC; capture margins in RDLC
                ReportPageSettings pageSettings = rpt.GetDefaultPageSettings();
                strMargins = String.Concat((Convert.ToDouble(pageSettings.Margins.Top) / 40.0).ToString(), ",", (Convert.ToDouble(pageSettings.Margins.Left) / 40.0).ToString(), ",", (Convert.ToDouble(pageSettings.Margins.Bottom) / 40.0).ToString(), ",", (Convert.ToDouble(pageSettings.Margins.Right) / 40.0).ToString());
    
                //Capture report orientation
                orientation = pageSettings.IsLandscape ? "landscape" : "portrait";
    
                return GetDeviceInfo(uiCulture, format, strMargins, orientation);
            }
    
            public static StringReader FormatReportForTerritory(string reportPath, string orientation, string uiCulture)
            {
                XmlDocument xmlDoc = new XmlDocument();
                Assembly asm = Assembly.GetExecutingAssembly();
                Stream xmlStream;
    
                xmlStream = asm.GetManifestResourceStream(reportPath);
    
                try
                {
                    xmlDoc.Load(reportPath);
                }
                catch
                {
                    //Ignore??!?
                }
    
                XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
                nsmgr.AddNamespace("nm", "http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition");
                nsmgr.AddNamespace("rd", "http://schemas.microsoft.com/sqlserver/reporting/reportdesigner");
    
                if ((uiCulture == "en-US") && (orientation == "landscape"))
                {
                    xmlDoc.SelectSingleNode("//nm:Page/nm:PageWidth", nsmgr).InnerText = "11in";
                    xmlDoc.SelectSingleNode("//nm:Page/nm:PageHeight", nsmgr).InnerText = "8.5in";
                }
                else if ((uiCulture == "en-US") && (orientation == "portrait"))
                {
                    xmlDoc.SelectSingleNode("//nm:Page/nm:PageWidth", nsmgr).InnerText = "8.5in";
                    xmlDoc.SelectSingleNode("//nm:Page/nm:PageHeight", nsmgr).InnerText = "11in";
                }
                else if (!(uiCulture == "en-US") && (orientation == "landscape"))
                {
                    xmlDoc.SelectSingleNode("//nm:Page/nm:PageWidth", nsmgr).InnerText = "29.7cm";
                    xmlDoc.SelectSingleNode("//nm:Page/nm:PageHeight", nsmgr).InnerText = "21cm";
                }
                else if (!(uiCulture == "en-US") && (orientation == "portrait"))
                {
                    xmlDoc.SelectSingleNode("//nm:Page/nm:PageWidth", nsmgr).InnerText = "21cm";
                    xmlDoc.SelectSingleNode("//nm:Page/nm:PageHeight", nsmgr).InnerText = "29.7cm";
    
                }
    
                StringReader rdlcOutputStream = new StringReader(xmlDoc.DocumentElement.OuterXml);
    
                return rdlcOutputStream;
            }
    
            public static StringReader LoadReportXml(string reportPath, string orientation)
            {
                XmlDocument xmlDoc = new XmlDocument();
                Assembly asm = Assembly.GetExecutingAssembly();
                Stream xmlStream;
    
                xmlStream = asm.GetManifestResourceStream(reportPath);
    
                try
                {
                    xmlDoc.Load(reportPath);
                }
                catch
                {
                    //Ignore??!?
                }
    
                XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
                nsmgr.AddNamespace("nm", "http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition");
                nsmgr.AddNamespace("rd", "http://schemas.microsoft.com/sqlserver/reporting/reportdesigner");
    
                StringReader rdlcOutputStream = new StringReader(xmlDoc.DocumentElement.OuterXml);
    
                return rdlcOutputStream;
            }
    
    
            public static Byte[] RenderReportToMemoryAsPDFInAnotherAppDomain(string path, string reportName, string reportSourceName, DataTable dt, Dictionary<string, string> parameters, string currentTerritory)
            {
                var ads = new AppDomainSetup();
                ads.ApplicationBase = Path.Combine(path, "Bin");
                ads.PrivateBinPath = ads.ApplicationBase;
                var appDomain = AppDomain.CreateDomain("SSRS Domain", null, ads);
                var repHelper = appDomain.CreateInstanceAndUnwrap("Demonstrations", "Demonstrations.Business.ReportHelperInAppDomain") as ReportHelperInAppDomain;
                Byte[] data = repHelper.RenderReportToMemoryAsPDF(reportName, reportSourceName, dt, parameters, currentTerritory);
                AppDomain.Unload(appDomain);
                return data;
            }
        }
    
        public class ReportHelperInAppDomain : MarshalByRefObject
        {
            public ReportHelperInAppDomain() { }
    
            // Reports Load
            public static void LoadSubReport(LocalReport rpt, string reportName, string reportPath)
            {
                rpt.LoadSubreportDefinition(reportName, File.OpenRead(reportPath));
            }
    
            // Data Sources
            public static void AddDataSource(LocalReport rpt, string dataSourceName, DataTable dataTableName)
            {
                rpt.DataSources.Add(new ReportDataSource(dataSourceName, dataTableName));
            }
    
            // Parameters
            public static void SetSingleParameter(LocalReport rpt, string paramName, string paramValue)
            {
                var rptParam = new ReportParameter(paramName, paramValue);
                rpt.SetParameters(new ReportParameter[] { rptParam });
            }
    
            public Byte[] RenderReportToMemoryAsPDF(string reportName, string reportSourceName, DataTable dt, Dictionary<string, string> reportParameters, string currentCulture)
            {
                using (var rpt = new LocalReport())
                {
                    rpt.EnableExternalImages = true;
                    rpt.ReportPath = reportName;
                    AddDataSource(rpt, reportSourceName, dt);
    
                    foreach (string key in reportParameters.Keys)
                        SetSingleParameter(rpt, key, reportParameters[key]);
    
                    Byte[] data = rpt.Render("PDF", GetDeviceInfoFromReport(rpt, currentCulture, "PDF"));
                    return data;
                }
            }
    
            private static string GetDeviceInfo(string uiCulture, string format = "pdf", string marginscsv = "0,0,0,0", string orientation = "portrait")
            {
                string deviceInfo;
                string[] margins = marginscsv.Split(',');
    
                string tm = margins[0]; // Top margin
                string lm = margins[1]; // Left margin
                string bm = margins[2]; // Bottom margin
                string rm = margins[3]; // Right margin
    
                if (uiCulture == "en-US")
                {
                    deviceInfo = (orientation == "portrait") ? "<PageWidth>8.5in</PageWidth><PageHeight>11in</PageHeight>" : "<PageWidth>11in</PageWidth><PageHeight>8.5in</PageHeight>";
                }
                else
                {
                    deviceInfo = (orientation == "portrait") ? "<PageWidth>21cm</PageWidth><PageHeight>29.7cm</PageHeight>" : "<PageWidth>29.7cm</PageWidth><PageHeight>21cm</PageHeight>";
                }
    
                deviceInfo = "<DeviceInfo>" + "<OutputFormat>" + format + "</OutputFormat>" + deviceInfo + "  <MarginTop>" + tm + "cm</MarginTop>" + "  <MarginLeft>" + lm + "cm</MarginLeft>" + "  <MarginRight>" + rm + "cm</MarginRight>" + "  <MarginBottom>" + bm + "cm</MarginBottom>" + "</DeviceInfo>";
    
                return deviceInfo;
            }
    
            public static string GetDeviceInfoFromReport(LocalReport rpt, string uiCulture, string format)
            {
                string strMargins;
                string orientation;
    
                //Render overwrites margins defined in RDLC; capture margins in RDLC
                ReportPageSettings pageSettings = rpt.GetDefaultPageSettings();
                strMargins = String.Concat((Convert.ToDouble(pageSettings.Margins.Top) / 40.0).ToString(), ",", (Convert.ToDouble(pageSettings.Margins.Left) / 40.0).ToString(), ",", (Convert.ToDouble(pageSettings.Margins.Bottom) / 40.0).ToString(), ",", (Convert.ToDouble(pageSettings.Margins.Right) / 40.0).ToString());
    
                //Capture report orientation
                orientation = pageSettings.IsLandscape ? "landscape" : "portrait";
    
                return GetDeviceInfo(uiCulture, format, strMargins, orientation);
            }
        }
    }
    


    www.kiquenet.com/profesional

    Tuesday, December 01, 2015 7:25 AM
  • Issues using another Appdomain and delegates, Function, Action

    http://stackoverflow.com/questions/1510466/replacing-process-start-with-appdomains

    I use List<Tuple<string, Func<IEnumerable>>>


    I have this source code

                List<TablaEvolucionValorAnterior> listaPolizas = DatosGrafica.GetTablaEvolucionPolizasVentas(empresa, mediador);
                List<TablaEvolucionValorAnterior> listaPrimas = DatosGrafica.GetTablaEvolucionPrimasVentas(empresa, mediador);
                List<TablaRamosVentas> listaRamos = DatosGrafica.GetTablaRamosVentas(empresa, mediador);
    
              
                var dataSources = new List<Tuple<string, Func<IEnumerable>>> 
                {
                    Tuple.Create<string, Func<IEnumerable>>("TablaEvolucionVentasPolizas", () => { return listaPolizas; }),
                    Tuple.Create<string, Func<IEnumerable>>("TablaEvolucionVentasPrimas", () => { return listaPrimas; }),
                    Tuple.Create<string, Func<IEnumerable>>("TablaRamosVentas", () => { return listaRamos; }),
                };

    Any suggestions?


    www.kiquenet.com/profesional

    Tuesday, December 01, 2015 9:39 AM
  • I had the same issue.

    We had a 2 Iif(Fields!MyFieldName.Value,"*","") in a matrix in our report that took 50% of the time.

    Using the Perf analyzer in Vs Premium, I was able to find that the Render method was using 75% of the time and that each of the methods that have to fill those fields were using 25%.

    I removed the Iif and added a property in the dataobject like "string MyValueBool {get { return MyField?"*":"";}}

    Thursday, May 05, 2016 8:37 PM
  • Is it oficially resolved by now?

    Alexander Yaremchuk

    Thursday, March 23, 2017 7:21 AM