Using DataSet and CustomBean together to build template (LINQ)

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, <<[map.get(“something”)]>>, 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<Product> products) {
        mCustomer = customer;
        mPrice = price;
        mProducts = products;
    }

    public String getCustomer() {
        return mCustomer;
    }

    public double getPrice() {
        return mPrice;
    }

    public Iterable<Product> getProducts() {
        return mProducts;
    }

    private final String mCustomer;
    private final double mPrice;
    private final Iterable<Product> 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<Contract> 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<Contract> fetchContracts(){

        // Here mManagerId should be used, but we return just an example collection.
        return Arrays.asList(
            new Contract("Customer A", 500, Arrays.asList(new Product("Product A1"), new Product("Product A2"))),
            new Contract("Customer B", 300, Arrays.asList(new Product("Product B1"), new Product("Product B2")))
        );
    }

    private final int mManagerId;
    private Iterable<Contract> mContracts;
}

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

// Create a template document.
DocumentBuilder builder = new DocumentBuilder();
builder.writeln("Create date: <<[getCreateDate()]>>");
builder.writeln("Author: <<[getAuthor()]>>");
builder.writeln("Contracts: <<foreach [in getContracts()]>>");
builder.writeln("  Customer: <<[getCustomer()]>>");
builder.writeln("  Contract price: <<[getPrice()]>>");
builder.writeln("  Products: <<foreach [in getProducts()]>>");
builder.writeln("   - <<[getName()]>><</foreach>><</foreach>>");

// 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<String, String> mFieldValues = new HashMap<String, String>();

    /**
     * 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: <<[firstName]>>");
builder.writeln("Last name: <<[lastName]>>");

// 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:

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.getValue() method. This method returns Object class type.
Even getFieldType does not support generics. I am not sure why it is not able to iterate over custom beans or datareader.

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.
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.

Hi Rakesh,

Thanks for your inquiry. We are working over your query and will get back to you soon.

Best regards,

Any update on this.

Hi Rakesh,

Please check your original requirement. You asked for using of different objects in the same template. Our new feature of accessing of multiple data source objects covers this request. Moreover, the feature was agreed with you here and here before implementing.

But now it appears that you want to use the same name for different data sources. This is a new requirement from you. At the moment, there is no way to achieve this. However, we have the following notes:

  1. You do not need to use prefixes for “tables” at all. Instead, you just need to use a prefix for IDataRecord providing all “fields”. That is, if you pass “bean” and “table1” as names of data sources to ReportingEngine.buildReport, you may use the following syntax in your templates: “bean.field1” and “table1”.

  2. Due to the fact that we had a few requests on shortening of expression syntax, we were thinking of introducing of expression aliases. That is, for example, an expression like “obj.field1.field2…” could be shortened to just “alias1”. With this way, your requirement in particular could be satisfied as well. However, the feature request is not registered in our issue tracking system yet.

  3. Regarding your request about using of nested IDataRecord/IDataReader instances, it is not supported by design as explained here.

Please feel free to ask if you have any questions.

Best regards,

A post was split to a new topic: Introducing of expression aliases in LINQ Reporting Engine