I am working on an application that requires several types of Crystal Reports, "standard" ones that hit a database table, update connection string and refresh the report, done. The other type of Crystal Report is based on a regular (slightly complex) class object, or POCO object.
The problem I'm facing is that the datasource of the report is based on a class we've created, it has properties that are also classes we've created. When I refresh the report, the data in the object is updated, but the data in the child property objects are not updated. They remain as the values set when I created the report.
A bit of background, the environment is C# on VS2010 with Crystal Reports 2011.
In order to create the report, I created an object and populated every property with relevant data, including child objects, I then exported the object to XML and outputted it to a file. I then created a new report and added a datasource of the type "ADO.NET (XML)".
All the "tables" appeared fine and I was able to make and add links, design and preview the report as you would normally.
When it came to runtime testing, I started with the code from this StackOverflow question:
.NET - Convert Generic Collection to DataTable
in order to convert the list of my objects into a DataTable and assign it as a datasource for the report. As mentioned previously, it works for the first level, but not child properties.
I've modified that code to create a new DataTable when a child property is one of our created classes and not simply a CLR data type, but that just leaves me with an empty report now. That code is below:
public static class CollectionExtensions
{
/// <summary>Converts to.</summary>
/// <typeparam name="T">The type value</typeparam>
/// <param name="list">The list value.</param>
/// <returns>The data table.</returns>
public static DataTable ConvertTo<T>(this IList<T> list)
{
var entityType = typeof(T);
var table = CreateTable(entityType);
foreach (var item in list)
{
var row = ConvertToRow(table, item, entityType);
table.Rows.Add(row);
}
return table;
}
/// <summary>
/// Converts to table.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="item">The item value.</param>
/// <param name="type">The type value.</param>
/// <returns>returns a data table</returns>
public static DataRow ConvertToRow(DataTable table, object item, Type type)
{
var properties = TypeDescriptor.GetProperties(type);
var row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
{
if (prop.PropertyType.IsAssignableFrom(typeof(AbstractEntity)))
{
var subTable = CreateTable(prop.PropertyType);
if (prop.GetValue(item) != null)
{
var subRow = ConvertToRow(subTable, prop.GetValue(item), prop.PropertyType);
subTable.Rows.Add(subRow);
}
row[prop.Name] = subTable;
}
else
{
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
}
}
return row;
}
/// <summary> Creates the table. </summary>
/// <param name="type">The type value.</param>
/// <returns>The datatable</returns>
public static DataTable CreateTable(Type type)
{
var table = new DataTable(type.Name);
var properties = TypeDescriptor.GetProperties(type);
foreach (PropertyDescriptor prop in properties)
{
if (prop.PropertyType.IsAssignableFrom(typeof(AbstractEntity)))
{
table.Columns.Add(prop.Name, typeof(DataTable));
}
else
{
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
}
}
return table;
}
}
I think the problem is that I've got a disconnect between how I'm creating the report and how the data is being applied at runtime when the datasource is being updated.
But never having done an XML-based Crystal Report before, I'm not exactly sure how to resolve this. My questions to the experts out there are:
- Am I on the right track, for creating the report in the first place?
- Given how I'm creating the report, am I on the right track for how I'm updating the report?
- Is there a better way to achieve this same result? For a multi-level object being the datasource for an XML-based Crystal Report.