How to create report (RDLC) without database? - c#

Problem
When you create a report (RDLC) the datasource seems to be only this or that database. Is there any way to convince VS to establish a link to memory data source? Something similar to WPF databinding.
The issue is, I would like to create a report with just a few data (entered by user), the whole point is layout, I don't have massive amount of data. So installing DB, writing data to DB, and then fetching them just to show the report is huge overkill.
So, I am looking for ability to create a report from memory data.
Background
I would like to design a layout, add images, set styles, font colors, etc. and add no more than few parameters like "first name", "last name" (of user) and "text". User would enter those 3 values, get a flyer and print it X times.
The layout has to be exact -- starting from paper size, the placement of images, size of fonts, etc.
Maybe there are better solutions than RDLC but it is built-in engine, and no matter how I search it always pops out in search results.

The datasource for an RDLC report can be anything that implements IEnumerable. If it is an enumeration of objects, then the properties on the object become fields in the report.
The thing about reports is they have their own internal notion of what the dataset is. At design time you need to provide the report designer with a dataset to work with. The report ingests that dataset internally and it is used to design the report. The reality is the report itself doesn't care about the actual dataset. It only cares about its schema. However, at runtime the objects you provide to satisfy that dataset can come from anywhere, as long as they satisfy that same schema.
I have a little blog post from back in my MS days that shows a trick on how to get good design time support, and then at runtime provide the report with any data you want:
http://blogs.msdn.com/b/magreer/archive/2008/10/16/setting-the-datasource-for-a-report-at-runtime.aspx
update Microsoft has since deleted my blog, but I found it in the wayback machine
https://web.archive.org/web/20160204041848/http://blogs.msdn.com/b/magreer/archive/2008/10/16/setting-the-datasource-for-a-report-at-runtime.aspx

I recently wrote a blog post on creating a reporting assembly and using it in a project. My reports accept a list of my classes as a datasource and dont read from the DB themselves.
If you have a look here:
http://wraithnath.blogspot.com/2011/02/visual-studio-2010-report-viewer-object.html
it should help. Basically you create a class library containing the datasources as VS 2010 has a real problem detecting object datasources. It works like 20% of the time which is why i decided to do it this way.
N

You can definitely bind to DataTables. Since you can create DataTables by hand, that's one way to do this without a database.
Here's an example where we programmatically load an RDLC control in order to render a PDF, using DataTables:
Dim Viewer As New ReportViewer
Viewer.LocalReport.ReportPath = "Physicians\Patients\OrderPlacement\DownloadRx\RxPdf.rdlc"
Me.LoadReport(orderID, Viewer)
Dim Renderer As New Code.Reporting.RenderToPDF
Renderer.Save(Viewer, FileFullPath)
And here are the contents of LoadReport:
Private Sub LoadReport(ByVal orderID As Integer, ByVal viewer As ReportViewer)
'This is adapted from here: http://www.codeproject.com/KB/reporting-services/RDLC_and_DataSet.aspx
'--Setup
viewer.LocalReport.DataSources.Clear()
viewer.LocalReport.EnableHyperlinks = True
'--Configure DataSources
Dim DocumentData As New RxDocumentData(orderID)
Me.SetupRxPdfDataSourceHeader(DocumentData, viewer)
Me.SetupRxPdfDataSourceMetrics(DocumentData, viewer)
Me.SetupRxPdfDataSourceOrderHeader(DocumentData, viewer)
Me.SetupRxPdfDataSourceOrderItems(DocumentData, viewer)
Me.SetupRxPdfDataSourceChainOfCustody(DocumentData, viewer)
Me.SetupRxPdfDataSourcePreTreatmentWorkupOrderTags(DocumentData, viewer)
Me.SetupRxPdfDataSourceTakeHomeMedicationsOrderTags(DocumentData, viewer)
viewer.LocalReport.Refresh()
End Sub
And here's one of those little configuration methods:
Private Sub SetupRxPdfDataSourceHeader(ByVal data As RxDocumentData, ByVal viewer As ReportViewer)
Dim Dset_Header As New ReportDataSource("Dset_Header", data.HeaderDataTable)
viewer.LocalReport.DataSources.Add(Dset_Header)
End Sub
data.HeaderDataTable is just a strongly typed DataTable that we create programmatically and put data into by hand.
There's nothing special about the DataTable, but getting to the point where this code was functional probably took a solid week. Hope this helps.

You can manually create a DataTable object, populate the Columns collection in there, then call NewRow(). Take the result of that and fill the fields, then pass it to Rows.Add(). That's what I've been doing (really don't like rdlc, it's so slow and clunky compared to html).

Return a list of your business objects and add it as the data source:
ReportViewer.LocalReport.DataSources.Add(new ReportDataSource("Report", new List<ReportDto> { new ReportDto(businessObj) }));
ReportDto is a wrapper for your business object where all formatting, concatenations and other report related modifications are done. It emits only the properties you need for the report.
Then go to add data set and pick the ReportDto's namespace as the data source and pick ReportDto as the dataset. Now all the properties you have included in ReportDto will be available in the designer.

Related

Sorting by FormulaField in Crystal Reports

I've begun the task of bringing our CR to the 21st century. The templates were created more than 10 years ago, as well as the report generator application written in Visual Basic that allows a user to pick different options before they click a button for generating a report.
So far the process hasn't been too bad but I've stumbled upon something that I can't figure out.
In VB, there's a line of code that sorts the report by a database table field:
Report.RecordSortFields.Add Report.Database.Tables(1).fields.GetItemByName("Name"), crAscendingOrder
I've successfully got that same logic working using the lines below
DatabaseFieldDefinition databaseFieldDefinition = Report.Database.Tables["Bureau"].Fields["Name"];
SortField sortField = Report.DataDefinition.SortFields[0];
sortField.Field = databaseFieldDefinition;
sortField.SortDirection = SortDirection.AscendingOrder;
The other type of sorting is what I'm struggling with. The problem is if that DataDefinition only contains a single SortField using a database tables field, I can't seem to make it order using a formula field. If you iterate through the formula fields you can see multiple fields like #Name, #XX, however if you look in the sort field it looks like it's specifying just a table like 'Assets.Name'
In the original code, this was done simply using the this VB line of code:
Report.RecordSortFields.Add Report.FormulaFields.GetItemByName("AssetName"), crAscendingOrder
For report files that already have a sort field with an #XXX, it works because I'm just replacing the SortField with this new FormulaDefinition that I've got from the below code (that also uses the #XXX), however if the sort field is only using a 'Table.Field' it doesn't work
Report.DataDefinition.FormulaFields["AssetName"];
My obvious first guess is that I need to modify the report file to get this working, however the report files are still allowing sorting via formulafield even in our old VB app, meaning if they could do it back 10+ years ago in vb I should be able to do it in C# without modifying the report.

Create report from dynamically built dataset

I'm having trouble coming up with a way to generate reports (either via xlsx or MS report viewer). The biggest issue is my dataset being dynamically created. Here's what I have:
One or more terminals periodically fill a cloud-hosted database with information about where the info is coming from, the global key and the value corresponding to the pair (origin, key).
So I end up with a table like:
TERMINAL_NO|GLOBAL_KEY|DECIMAL_VALUE
===========|==========|=============
123 |9876 |1.00
123 |9875 |0.50
123 |9872 |-4.00
234 |9876 |3.00
234 |9875 |5.45
234 |9872 |2.50
And I have made an app that transforms that into a DataSet with one column per TERMINAL_NO, and each row is every distinct GLOBAL_KEY, storing the corresponding DECIMAL_VALUES for each pair. However, that's the issue - I can't find a way to generate reports or xlsx files with dynamically created DataSets, as I understood both of them need typed DataSets to work with.
Is there an easier way to gather that data, or am I doing this wrong?
08-15-20 EDIT
As per #jdweng, I did try the method depicted there, but I can't make the reportViewer display the actual data. Currently I have the following:
ReportDataSource rds = new ReportDataSource("DataSetStock", LoadData());
//LoadData() returns a filled datatable.
RView.ProcessingMode = ProcessingMode.Local;
RView.LocalReport.DataSources.Clear();
RView.LocalReport.DataSources.Add(rds);
RView.LocalReport.ReportPath = "StockReport.rdlc";
RView.RefreshReport();
Yet, all I get is an empty reportviewer inside the winformshost control.
As per #nbk's suggestion, I went with a report generator code made by Nadir (https://www.codeproject.com/Tips/888174/Dynamically-Creating-an-RDLC-Report-Just-Using-a-D), by using the provided .cs files.
Just had to make a few changes on the xaml because I'm not using a logo image. I already had my dataset (just had to change the column names to be cls-compliant) and it worked flawlessly. I didn't have to use the userControl folder, as I'm working on WPF and already had a winforms host control with a reportviewer control on it.

Return a response to indicate "No Data" via the ReportExecution.RenderReport() API method

I am looking for a tip, trick or hack that will allow a return of an empty byte array, an out warning flag or some other indicator to let the caller of ReportExecution2005.RenderReport() be aware that no data was returned for the report.
I believe that I know what you're doing now, but correct me if I'm wrong.
You created the dataset and the required parameters in the report and set the stored procedure there. Then you just pass the parameters from code and then you just render the report which causes SSRS to automatically fill the dataset with respect to the parameters.
This is a good approach but this causes you to have no knowledge of the datasource until the report is rendered.
I believe the solution to your problem is to generate the datasource outside the reporter, so executing the procedure from code. Then you have access to any information about the dataset before sending it to the reporter. So if the procedure returned no data this allows you to not render the report at all.
And when you do render the report, you won't gave to execute the procedure anymore (efficiency).
The easiest way to achieve this is to use the Local Reporting
Services (often refered to as rdlc).
There is no need to use a shared datasource because you have the
wanted dataset already. You can just pass the datatable to the
reporter and it will render correctly without needing to execute the
procedure anymore.
There are many tutorials that should make this easy for you, here is
a simple example: Using a Local Reporting Services 2008 Report with
an ADO.NET Data Set
If you really insist on using the older Reporting Services then it'll
be slightly more complicated to setup but it's still possible. Here
is detailed tutorial: Using an ADO.NET DataSet as a Reporting
Services Data Source
Before rendering the report, you simply fill your dataset by executing the procedure. And check whether or not you wish to continue rendering the report.
Example:
SqlDataAdapter daVendor = new SqlDataAdapter();
daVendor.SelectCommand = cmd;
DataSet dsVendors = new DataSet();
daVendor.Fill(dsVendors);
if(dsVendors.Any()){
//Render the report (passing the dataset to the reporter) and attach it to the mail.
}
else {
//Don't render the report and create corresponding message to send as email.
}
I strongly suggets using the Local Reporting Services. You don't want to add extra functionality anyway, as you're only sending it as attachment to an email.
I hope this helps you along, if you have any more trouble just leave a comment.
Use visibility to mimic this. Check count of your dataset and if its 0 dont show anything and then also create a text box that says no data available or something like that and set the visibility to only show when count is 0. Hope this make sense.

Using a BindingSource to link a DataSet to a DataGridView, but there's no data

This is my first time working with DataSets and BindingSources, so please be gentle on me.
As part of a more complicated reporting system (I've distilled it down to a basic incarnation, but it still won't run correctly), I'm trying to pull data from a database using a DataSet problematically (that is, not set up via the designer). Here is the code I have so far:
// pull the data set
var dsReportData = new Reports2.ReportTest2();
dsReportData.BeginInit();
dsReportData.SchemaSerializationMode = SchemaSerializationMode.IncludeSchema;
// bind tha data set
BindingSource bsReportBinding = new BindingSource();
((ISupportInitialize)bsReportBinding).BeginInit();
bsReportBinding.DataMember = dsReportData.Tables[0].TableName;
bsReportBinding.DataSource = dsReportData;
bsReportBinding.ResetBindings(true);
// test this stuff
dgvTester.DataSource = bsReportBinding;
dsReportData.EndInit();
((ISupportInitialize)bsReportBinding).EndInit();
I based this on the code I saw in a .designer.cs file after setting up binding through the designer. dgvTester is just a DataGridView with the default properties created in the designer.
The ReportTest2 dataset has just one TableAdapter in it, added via designer.
Now, if I went to Data -> Preview Data in VS and previewed the ReportTest2.payments.Fill,GetData () object, it returns data just fine, same as if I ran the query I used to crate the TableAdapter in SQL Server Management Studio.
However, running the actual code results in the DataGridView getting the column names from the query result, but not the actual data. The debugger reveals that dsReportData.payments.Rows.Count == 0 (and that, yes, dsReportData.Tables[0] is payments).
I ultimately intend to use the BindingSource to provide data to a ReportViewer, but first things first is making sure there's no problems with retrieving the data before going onto debug the report.
Hopefully I'm missing something obvious here. I hope so...
Figured it out after some trial and error. This is the part I was missing:
var TableAdapter = new Reports2.ReportTest2TableAdapters.paymentsTableAdapter();
TableAdapter.Fill(dsReportData.payments);
I didn't see it in the code I was referencing because the designer snuck it into the .cs file instead of the .designer.cs file. This made the data appear.

How do Microsoft reports (RDL) query the data?

I am not creating RDL from scratch so maybe this is a problem -- I work on already prepared files.
MSDN states that CommandText in RDL file can contain T-SQL query. Ok, this I understand, but what else it can contains?
I am asking because the phrasing clearly indicates you can put some other expression there
So if I understand correctly, I can look at RDL code (in Visual Studio, RMB on RDL file, "view code") and the interesting parts would be...?
DataSourceName -- this is a "link" to database via definitions of data sources
CommandText -- I thought this is the place to put query, like SELECT... but from what I see there are no queries used
Reporting service, loads the rdl file into it, and starts parsing and reading the command according to their sections like
data source, report params, etc.
gets the values of params (if any). start using the data source database connection. execute the query/ sp command. get the data, and store in seperate data fields which are also mentioned in rdl. binds their values with controls (text box, grid columns etc), if there is any expression written into it, execute them as well.
Generate the output (html/ pdf).
And there you Go.
I just tried to explain in short and simple words. you can check out msdn for complete detail.
Regards,
Mazhar Karimi
You can create reports manually and fill them with any data that you would like to.
Sth like:
ReportDataSource reportDataSource = new ReportDataSource();
reportViewer.Reset();
reportDataSource.Name = "DataSetOdczyty_klienci_adresy";
reportDataSource.Value = klienciadresyBindingSource;
reportViewer.LocalReport.DataSources.Add(reportDataSource);
reportViewer.LocalReport.ReportEmbeddedResource = "Wodociagi.Reports.ReportListaKlientow.rdlc";
You can open the report file *.rdl with an XML editor like Notepad++. Then, search for <DataSets> and you will find the datasets used in the report.
The field names of each data set are in the <Fields> section
In the <Query> section of each data set you can find <CommandText> and <QueryParameters> as shown in the example below
Example:
<Query>
<DataSourceName>MyDataSource</DataSourceName>
<CommandType>StoredProcedure</CommandType>
<CommandText>usp_QueryCustomers</CommandText>
<QueryParameters>
<QueryParameter Name="#CustomerId">
<Value>=Parameters!PersSysId.Value</Value>
</QueryParameter>
<QueryParameter Name="#RowsCnt">
<Value>=Parameters!RowsCnt.Value</Value>
</QueryParameter>
</QueryParameters>
</Query>
I didn't find a way to see that in Visual Studio's report editor easily. Maybe the bounty I have started helps here (does someone like to earn the 50 reputation points)?
Initially, I was not sure why both the OP and #Matt are reading the XML directly instead of editing the query in Visual Studio (I only resort to that in extreme cases). But now I think you might have failed victims of the missing "Report Data" pane.
Open the report in Visual Studio BIDS like normally, then from View menu select "Report Data". If it's not there, click on the report canvas anywhere, then it should appear. In the "Report Data" pane that will appear, you're interested in Data Sources (where's the data coming from?) and Datasets (what are the queries, parameters, expressions?).

Categories