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.
Related
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.
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.
Suppose that you have a report CustomerReport.rdlc which is for one customer....
Is it possible to have multiple customers reports in the same reportviewer?
If not what is another solution ?
Yes it's possible to have one report for all customers. Basically you define your report template CustomerReport.rdlc to get data from some dataset (a stored procedure or some method in your datalayer).
for ex. your method should look something like this:
public DataTable GetCustomerDetails(int customerID)
{
//call stored procedure
}
Then on the page where is ReportViewer you do something like this:
DataTable data = GetCustomerDetails(1);
this.ReportViewer1.ProcessingMode = Microsoft.Reporting.WebForms.ProcessingMode.Local;
this.ReportViewer1.LocalReport.ReportPath = "CustomerReport.rdlc";
this.ReportViewer1.LocalReport.DataSources.Clear();
this.ReportViewer1.LocalReport.DataSources.Add(new ReportDataSource("[Datasource name defined in CustomerReport.rdlc]", data));
this.ReportViewer1.LocalReport.Refresh();
A single reportviewer control will show a single .rdlc document, along with any subreports included in the "parent" .rdlc "at a time". If you working in web forms it is also a best practice to not attempt to 'reload' a second report into a report viewer. It is better to empty the div containing the reportviewer and creating a new instance of the reportviewer control and insert that into the container (div/span/etc.)
After that it is just a matter of making sure you have the customer specific data assigned as the datasource.
Solution seems to be code generated rdlc!!
I've created a new project in .Net (2010 4.0) and added an SDF data file. I've generated a dataset and created a table in it (and I believe generated the Fill and other methods).
In code, I'm trying to add a row to the database.
eBureauScrubber.App_Data.matchingtempDataSet ds = new App_Data.matchingtempDataSet();
eBureauScrubber.App_Data.matchingtempDataSet.ctfFileRow row = ds.ctfFile.NewctfFileRow();
row.Address = "123 Main St.";
row.City = "Overland Park";
row.FirstName = "Matt";
row.LastName = "Dawdy";
row.rownum = 1;
EDIT: Added the next bit of code.
ds.ctfFile.Rows.Add(row);
ds.ctfFile.AcceptChanges();
ds.AcceptChanges();
eBureauScrubber.App_Data.matchingtempDataSetTableAdapters.ctfFileTableAdapter ctfa = new App_Data.matchingtempDataSetTableAdapters.ctfFileTableAdapter();
ctfa.Update(ds.ctfFile);
This runs fine. However, after the program completes, the data is not persisted in the database. What am I missing?
EDIT: I've tried all different combinations of AcceptChanges() on the datatable, the dataset, running update() before, after, etc. I'm missing something huge here. I'm not even sure it is connecting to the "right" database. Maybe that's my problem.
EDIT 2: Here's what I did to get this to work (it's still funky, though).
Change the properties of my DB file in App_Data to "Do Not Copy"
Manually copy that db file to bin\debug\app_data
Use the data adapter's fill method to fill the ds.ctfFile data table.
Create a row (.NewctfFileRow())
Set values on that row.
ds.ctfFile.Rows.Add(row)
ds.ctfFile.AcceptChanges();
ds.AcceptChanges();
Call the adapater's update method.
Now, the data is in my database file (in bin\debug\app_data), but I can't see it because the Data Sources connection. I'm still trying to find out how to do that.
It should have generated a TableAdapter class with a .Update() method that you have to call to save data in your database. See MSDN for some examples.
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.