We're sorry Aspose doesn't work properply without JavaScript enabled.

Free Support Forum - aspose.com

Using DataSet and CustomBean together to build template (LINQ)

Hi
I am not able to bind DataSet and CustomBean to single template.
ReportingEngine engine = new ReportingEngine();
// engine.buildReport(doc, senderCustomBean, "s");
engine.buildReport(doc, dataset, "ds");

My template has few simple fields and tables. I can use custom bean to populate fields
or dataset (data tables) to populate tables. However I am not able to figure out on how to
build report using both at the same time.

Can you please a provide a sample. Your help is much appreciated.

Thanks,
Rakesh

Hi Rakesh,


Thanks for your inquiry. We are in coordination with product team to get answer pertaining to your queries. Soon you will be updated with the required information.

Best regards,
Hi Rakesh,

Please note that it is not recommended to mix data source objects of special and custom types while building a single report. Typically, this can be avoided. For example, it is not necessary to create a DataTable instance to fill a document table with data. An enumeration of objects of a custom type can be used instead. The following page contains several examples on this:

Appendix C. Typical Templates

However, if your scenario requires data sources of both types to be used to build a single report, this can be achieved by using a fake single-column, single-row DataTable instance to access a custom object from a DataSet instance. Consider the following example:

// Create an ordinary data table.
DataTable dataTable1 = new DataTable("entities");
dataTable1.getColumns().add("name", String.class);
dataTable1.getRows().add("John Doe");
dataTable1.getRows().add("Jane Doe");
// Create a fake single-column, single-row data table to access a bean.
DataTable dataTable2 = new DataTable("bean");
dataTable2.getColumns().add("value", Bean.class);
dataTable2.getRows().add(new Bean());
// Create a data set and add both tables into it.
DataSet dataSet = new DataSet();
dataSet.getTables().add(dataTable1);
dataSet.getTables().add(dataTable2);
// Create a template document to output contents of both bean and ordinary data tables.
DocumentBuilder builder = new DocumentBuilder();
builder.writeln("Bean value:");
builder.writeln(" - <>");
builder.writeln("DataTable values:");
builder.writeln("<> - <>");
builder.writeln("<>");
// Build a report and print its text.
ReportingEngine engine = new ReportingEngine();
engine.buildReport(builder.getDocument(), dataSet);
System.out.println(builder.getDocument().toTxt());

Hope, this helps.

Best regards,

Hi Awais,

The solution you provided works perfectly fine. We have huge set of custom fields and user can use any of these merge fields in the document. Will it be fine to use single row table with too many columns (approx. 800 columns) or is there any way to use java Map i.e., populate java Map with all merge fields and use it in the template.
Ex: java.util.Map<String,String> map = new java.util.HashMap<String, String>();
map.put(“ReportName”, “Test Report”);
map.put(“Description”, “Testing…”);

Is there any way to bind the above map to the template. Appreciate your help.

Thanks,
Hi Rakesh,

Thanks for your inquiry. We are in coordination with product team to get answer pertaining to your queries. Soon you will be updated with the required information.

Best regards,
Hi Rakesh,

I am afraid your requirement is not clear. Do you mean using of a single map (without data tables) as a data source? If so, then you definitely can do this accessing map contents in a template using the same syntax as would be used in Java code (for example, <>, where “map” is a data source name).

However, if you want to use a map in addition to using of a data set, then it is possible but would not give any advantages, since you would still need to use the same single-column, single-row data table workaround to achieve this.

So please clarify your requirements first. Thanks.

Also, we have noticed that you requested a new feature registered as WORDSNET-13370. Although it is possible to implement it, there is a little practical value from this feature. The reason is that LINQ Reporting Engine’s expressions are not just references to fields in contrast to mail merge. But the problem is that our customers still do not distinguish the two. So they try to apply the same approaches to LINQ Reporting Engine as they applied to mail merge which sometimes is wrong (and I believe that WORDSNET-13370 is the case). In this regard, could you please share more details about your scenario? How do you gather your data? Making SQL queries to a database and/or obtaining some values through GUI? Do you use an ORM or java.sql.* classes?

Considering these details, we will be able to provide a complete solution for your scenario rather than some scattered advice to use some workarounds here and there. Also, it should be helpful for other our customers facing similar scenarios. Thanks for your cooperation on that matter.

Best regards,

Below is our use case:

1. We defined huge set of merge fields and share with end user (Field1, Field2, … Field 999; these values are calculated on the fly for the selected fields)
2. End user will create a new word document with set of merge fields and uploads to our server
(Ex: doucment can have some random fields: Field99, Field176, Field685)
3. And any user can generate the report with this document by selecting required id’s
(Once user selects Id/primary key, we retrieve data associated for that key)

Regarding WORDSNET-13370:
With mail merge implementation, we retrieved names of the fields defined by end user (getMailMerge().getFieldNames()) and used Java Reflection & ORM to calculate these fields on the fly. But in LINQ, we are not able to find an easy way to get names of the fields defined in the report. Now we are parsing document word by word to find the field names.

Naming pattern:
Once the fields are known, it worked fine with single row data table. But end users were not convenient with syntax. (Ex: ds.data.single().Field1). Is there any shorthand notation for these expressions?
Other question was using Map instead of single row datatable. But again it’s not convient to use <<[map.get(“something”)]>> syntax. Is there any shorter way like map.something.

Let me know if you have any questions.

Thanks for helping me on this.


Hi Rakesh,

Thanks for your inquiry. We are in coordination with product team to get answer pertaining to your queries. Soon you will be updated with the required information.

Best regards,
Hi Rakesh,

If you use ORM, you actually do not need neither initializing of a DataSet nor extracting field names from templates nor using of reflection on his own, to implement your scenario. Here is an example of how this can be done. Let’s pretend, you have the following domain entities:

public class Product {
Product(String name) {
mName = name;
}

public String getName() {
return mName;
}

private final String mName;
}

public class Contract {
Contract(String customer,
double price, Iterable products) {
mCustomer = customer;
mPrice = price;
mProducts = products;
}

public String getCustomer() {
return mCustomer;
}

public double getPrice() {
return mPrice;
}

public Iterable getProducts() {
return mProducts;
}

private final String mCustomer;
private final double mPrice;
private final Iterable mProducts;
}

Then, to satisfy your requirements, the following class can be used to represent a data source object passed to the reporting engine:

public class ReportData {
ReportData(
int managerId) {
// Pass an identifier used to query a data collection here.
// It can be of any type, int is used as an example.
mManagerId = managerId;
}

public Date getCreateDate() {
// Return a field value. It can be extracted from a GUI element,
// fetched from a web-service or a database, etc.
// If obtaining of a particular field is an expensive operation,
// lazy initialization may be applied.
// The same approach can be applied to all Field1, Field2, etc.
return new Date();
}

public String getAuthor() {
// The same comments as for getCreateDate.
return "John Doe";
}

public Iterable getContracts() {
// Assuming that fetching of a data collection is an expensive operation,
// lazily initialize and cache the corresponding field.
if (mContracts == null) {
mContracts = fetchContracts();
}

return mContracts;
}

private Iterable fetchContracts(){
// Here mManagerId should be used, but we return just an example collection.
return Arrays.asList(

<span style=“font-size:10.0pt;font-family:“Courier New”;color:navy”>new
Contract(<span style=“font-size:10.0pt;font-family:“Courier New”;color:green”>“Customer
A”
, <span style=“font-size:10.0pt;font-family:“Courier New”;
color:blue”>500, Arrays.asList(<span style=“font-size:10.0pt;
font-family:“Courier New”;color:navy”>new
Product(<span style=“font-size:10.0pt;font-family:“Courier New”;color:green”>“Product
A1”
), <span style=“font-size:10.0pt;font-family:“Courier New”;
color:navy”>new
Product(<span style=“font-size:10.0pt;font-family:“Courier New”;
color:green”>“Product A2”
))),

<span style=“font-size:10.0pt;font-family:“Courier New”;color:navy”>new
Contract(<span style=“font-size:10.0pt;font-family:“Courier New”;color:green”>“Customer
B”
, <span style=“font-size:10.0pt;font-family:“Courier New”;
color:blue”>300, Arrays.asList(<span style=“font-size:10.0pt;
font-family:“Courier New”;color:navy”>new
Product(<span style=“font-size:10.0pt;font-family:“Courier New”;color:green”>“Product
B1”
), <span style=“font-size:10.0pt;font-family:“Courier New”;
color:navy”>new
Product(<span style=“font-size:10.0pt;font-family:“Courier New”;
color:green”>“Product B2”
)))

);

}



<span style=“font-size:10.0pt;font-family:“Courier New”;
color:navy”>private final int
<span style=“font-size:10.0pt;
font-family:“Courier New”;color:#660E7A”>mManagerId
;

<span style=“font-size:10.0pt;font-family:“Courier New”;
color:navy”>private
Iterable <span style=“font-size:10.0pt;
font-family:“Courier New”;color:#660E7A”>mContracts
;

}<o:p></o:p>

The following code snippet creates an example report using the above class:

// Create a template document.
DocumentBuilder builder = new DocumentBuilder();
builder.writeln(
"Create date: <>");
builder.writeln(
"Author: <>");
builder.writeln(
"Contracts: <>");
builder.writeln(
" Customer: <>");
builder.writeln(
" Contract price: <>");
builder.writeln(
" Products: <>");
builder.writeln(
" - <><><>");

// Build a report.
ReportingEngine engine = new ReportingEngine();
engine.buildReport(builder.getDocument(),
new ReportData(123));

// Print the report's content.
System.out.println(builder.getDocument().toTxt());

Notes to the example:

1. Members of domain entities (as well as of custom types like ReportData) can be referenced directly in templates. Thus, there is no need to wrap a collection of objects with a DataSet.

2. Once the reporting engine encounters a member invocation, it invokes the member and that’s it. Thus, simply to provide field values to the engine, there is no need to preliminarily analyze a template and extract names of fields (once again, template expressions are not just fields), calculate corresponding values through reflection, and add them to a map.

3. A drawback is, ReportData should contain members corresponding to all fields (i.e. “field1”, “field2”, etc.). Since the field set is huge, you may consider this as too hard to implement. But these fields are defined somewhere in code anyway. So in fact, you only need to move those definitions to this single ReportData class.

Hope, this helps.


Best regards,

Hi Aawis,

Thanks for the explanation.
It is impractical in our case to put all 800 fields in a single class. There is so much work done in constructors to pull appropriate module information and most of these classes can’t be combined.

Please let me know if you can think of any other possible solution.
And customers would not like to enter java method names; so instead of defining field as <<[getAuthor()]>>, can I define it as <<[author]>>

Thanks,
Rakesh
Hi Rakesh,

Thanks for your inquiry. We are in coordination with product team to get answer pertaining to your queries. Soon you will be updated with the required information.

Best regards,
Hi Rakesh,

To avoid combining of all fields in a single class and using of Java member names (which could be fields rather than methods, by the way), you may use the following class to pass its instance as a data source to the engine:

public class DataRecord implements com.aspose.words.net.System.Data.IDataRecord {
@Override
public int getFieldCount() {
return gFieldNames.length;
}

@Override
public Object get(int i) {
return getValue(i);
}

@Override
public String getName(int i) {
return gFieldNames[i];
}

@Override
public Class getFieldType(int i) {
// Assuming that values of all fields are strings.
// Object.class can be returned alternatively.
return String.class;
}

@Override
public Object getValue(int i) {
String name = getName(i);
String value = mFieldValues.get(name);

if (value == null) {
// Fetch a field value on the first demand and
// cache it for further use.
value = fetchValue(name);
mFieldValues.put(name, value);
}

return value;
}

private String fetchValue(String name) {
// Actual calculation of a field value should be implemented here.
// It may involve reflection, web service calls, or whatever.
return "value of " + name;
}

private final Map mFieldValues = new HashMap();

/**
* This static array MUST contain names of all possible fields.
*/
private static final String[] gFieldNames = new String[] {
"firstName",
"lastName"
};
}

Here is an example demonstrating using of the class:

// Create a template document.
DocumentBuilder builder = new DocumentBuilder();
builder.writeln(
"First name: <>");
builder.writeln(
"Last name: <>");

// Build a report.
ReportingEngine engine = new ReportingEngine();
engine.buildReport(builder.getDocument(),
new DataRecord());

// Print the report's content.
System.out.println(builder.getDocument().toTxt());


With this approach, you only need to know and keep all possible field names in the DataRecord class and then you may continue to use your approach in obtaining of field values via reflection or something else. Preliminary analysis of field names used in a template is not needed in this case.

However, at the moment, there is no way to pass multiple data source objects to the engine. Thus, a DataRecord instance cannot be passed together with a DataSet instance, for example. To resolve this, we may plan to add a new ReportingEngine.buildReport overload accepting multiple data source objects. Please tell us if this solution is acceptable to you and you would like us to implement passing of multiple data source objects to the engine?

Best regards,

Thanks for detailed explanation. I will implement your suggestion and update.


Thanks,


Hi Awais,

I was able to run your program and it worked fine for single value fields (like firstName, lastName).

Our report can have set of fields and set of tables. Calculating fields on the fly is solved with your suggestion. But for data tables, I was not able to figure out any solution.

Similar to fields, we have huge set of stored procedures; which are converted to DataTable and these needs to be calculated dynamically. Can you please help us in implementing function similar to below one.
public DataTable/Custom Iterator getFieldValue() { … }


I tried to implement Iterable interface like below
public class MyIterable implements  Iterable {
private List list = new ArrayList();
public MyIterable(DataTable dt) {
DataRowCollection rows = dt.getRows();
rows.get(0);
for(int i=0; i <rows.getCount(); i++) {
DataRow row = rows.get(i);
list.add(row);
}
}
@Override
public Iterator iterator() {
return list.iterator();
}
}

And modified DataRecord class to return Iterable object

public class DataRecord implements com.aspose.words.net.System.Data.IDataRecord {

@Override
public int getFieldCount() {
return gFieldNames.length;
}

@Override
public Object get(int i) {
return getValue(i);

}

@Override
public String getName(int i) {
return gFieldNames[i];
}

@Override
public Class getFieldType(int i) {
// Assuming that values of all fields are strings.
String name = getName(i);
// Object.class can be returned alternatively.
if(name.endsWith(“stName”)) {
return String.class;
} else {
return MyIterable.class;
}

}

@Override

public Object getValue(int i) {
String name = getName(i);
Object value = mFieldValues.get(name);
if (value == null) {
// Fetch a field value on the first demand and

// cache it for further use.

if(name.endsWith(“stName”)) {
value = fetchValue(name);
} else {
DataTable dt = new DataSource(739091).getClaims(); // Fetching dataTable
value = new MyIterable(dt);
}

mFieldValues.put(name, value);

}

return value;

}

private String fetchValue(String name) {
// Actual calculation of a field value should be implemented here.
// It may involve reflection, web service calls, or whatever.
return "value of " + name;

}

private final Map<String, Object> mFieldValues = new HashMap<String, Object>();

/**
*
* This static array MUST contain names of all possible fields.
*
*/

private static final String[] gFieldNames = new String[] {

“firstName”, “lastName”, “midstName”, “claims” };

}

But it is throwing below exception
Exception in thread “main” java.lang.NullPointerException
at asposewobfuscated.zzZD.zzY(Unknown Source)
at asposewobfuscated.zzWJ.zzZ(Unknown Source)
at asposewobfuscated.zzOQ.zzZ(Unknown Source)
at asposewobfuscated.zzYG.zzZ(Unknown Source)
at asposewobfuscated.zzYG.zzXK(Unknown Source)
at asposewobfuscated.zzYG.zzXL(Unknown Source)
at asposewobfuscated.zzYG.zzXM(Unknown Source)
at asposewobfuscated.zzYG.zzXN(Unknown Source)
at asposewobfuscated.zzYG.zzXO(Unknown Source)
at asposewobfuscated.zzYG.zzXP(Unknown Source)
at asposewobfuscated.zzYG.zzXQ(Unknown Source)
at asposewobfuscated.zzYG.zzXR(Unknown Source)
at asposewobfuscated.zzYG.zzXS(Unknown Source)
at asposewobfuscated.zzYG.zzXT(Unknown Source)
at asposewobfuscated.zzYG.zzXU(Unknown Source)
at asposewobfuscated.zzYG.zzXV(Unknown Source)
at asposewobfuscated.zzYG.zzXW(Unknown Source)
at asposewobfuscated.zzYG.zzXZ(Unknown Source)
at asposewobfuscated.zzRE.zzZ(Unknown Source)
at asposewobfuscated.zzR1.zzQC(Unknown Source)
at asposewobfuscated.zzX4.zzWI(Unknown Source)
at asposewobfuscated.zzX4.zzWJ(Unknown Source)
at asposewobfuscated.zzR1.zzZ(Unknown Source)
at asposewobfuscated.zzGY.zzZ(Unknown Source)
at com.aspose.words.ReportingEngine.buildReport(Unknown Source)
at com.aspose.words.ReportingEngine.buildReport(Unknown Source)


Can you please help me in resolving above issue. Appreciate your help on this.

Thanks,


Hi Rakesh,


Thanks for your inquiry. You encounter an exception because you are using DataRow in an unintended way. I am afraid, we won’t be able to fix this.

As shared in my previous reply, to meet your requirements, you need to use a DataRecord instance together with other data source instances simultaneously. To let you do this, we need to add a new ReportingEngine.buildReport overload accepting multiple data source objects.

Since you are satisfied with implementation of DataRecord to output “just fields” to a report, we can proceed with implementation of passing multiple data source objects to the engine (thus, enabling you to use “tables” as well). Your thread has been linked to the appropriate issue (WORDSNET-13538) and you will be notified as soon as it is resolved.

Once we implement the new ReportingEngine.buildReport overload, we will most likely provide an example on how to use it in your scenario.

Best regards,

Hi Awais,

Thanks for the update. You’ve been very helpful.

Thanks,

The issues you have found earlier (filed as WORDSNET-13538) have been fixed in this .NET update and this Java update.


This message was posted using Notification2Forum from Downloads module by aspose.notifier.
(1)

Hi Rakesh,


Awais:
Once we implement the new ReportingEngine.buildReport overload, we will most likely provide an example on how to use it in your scenario.
We have also attached example code with this post. Hope, this helps.

Best regards,

Hi Awais,

I am still not able to wrap fields and tables in single bean.

I have created IDataRecord implementor; added fields (field1, field2) and table (table1). I am getting below exception

Error:: Can not resolve method ‘getCol1’ on type 'class java.lang.Object’

Below is source:

<pre style=“font-family: “Courier New”; font-size: 9pt; background-color: rgb(255, 255, 255);”>package linq.bean;

import com.aspose.words.DocumentBuilder;
import com.aspose.words.ReportingEngine;

import java.util.ArrayList;
import java.util.List;

public class TestBean {

public static void main(String[] args) throws Exception{
DocumentBuilder builder = new DocumentBuilder();
builder.writeln(“Field1: <<[bean.field1]>>”);
builder.writeln(“Field2: <<[bean.field2]>>”);
builder.writeln();
builder.writeln("<<foreach [d in bean.table1]>> <<[d.getCol1()]>>");
builder.writeln("<>");
ReportingEngine engine = new ReportingEngine();

engine.buildReport(builder.getDocument(), new TestRecord(), “bean”);
System.out.println(builder.getDocument().toTxt());

}

public static class TestRow {
private String col1;
private String col2;

public TestRow(String column1, String column2) {
this.col1 = column1;
this.col2 = column2;
}
public String getCol1() {
return col1;
}

public void setCol1(String col1) {
this.col1 = col1;
}

public String getCol2() {
return col2;
}

public void setCol2(String col2) {
this.col2 = col2;
}
}

public static class TestRecord implements com.aspose.words.net.System.Data.IDataRecord {

private String[] fieldNames = new String[] {“field1”, “field2”, “table1”};

@Override
public int getFieldCount() {
return fieldNames.length;
}

@Override
public Object get(int i) {
return getValue(i);
}

@Override
public String getName(int i) {
return fieldNames[i];
}

@Override
public Class getFieldType(int i) {
if(getName(i).equals(“table1”)) {
return Iterable.class;
}
return String.class;
}

@Override
public Object getValue(int i) {
if(getName(i).equals(“table1”)) {
TestRow row1 = new TestRow(“F01”, “F02”);
TestRow row2 = new TestRow(“F03”, “F04”);

List list = new ArrayList<>();
list.add(row1);
list.add(row2);
return list;
}
return “Data”+getName(i);
}



}

}

I think issue is with IDataRecord.<span style=“font-family: “Courier New”; font-size: 9pt; color: rgb(51, 51, 51);”>getValue() method. This method returns Object class type.

Even <span style=“font-family: “Courier New”; font-size: 9pt; background-color: rgb(228, 228, 255);”>getFieldType does not support generics. I am not sure why it is not able to iterate over custom beans or datareader.
<span style=“font-family: “Courier New”; font-size: 9pt; color: rgb(51, 51, 51);”>
<span style=“font-family: “Courier New”; font-size: 9pt; color: rgb(51, 51, 51);”>As new release of Aspose supports multiple datasources, I can inject separate datarecord implementor for fields and seperate DataReaders for tables. But issue with that approach is I am not able to use same prefix.
<span style=“font-family: “Courier New”; font-size: 9pt; color: rgb(51, 51, 51);”>Our users would prefer bean.field1 and bean.table1. They don’t want use different prefixes for fields and tables. Is there any way to give same prefix for different datasources.