MailMerge.ExecuteWithRegions - Error - Value cannot be null. Parameter name: startParent

Hello there,

I am facing an error while calling MailMerge.ExecuteWithRegions(List).

Attached is my source document in which I have two table regions defined:
TableStart:Properties
TableStart:Applicants
TableEnd:Applicants
TableEnd:Properties

and my data source is a List, where T is my Business Entity.
Every T is having a collection of child objects say, Applicants.
So, I have implemented IMailMergeDataSource as below:

public class PropertyMailMergeDataSource: IMailMergeDataSource
{
    private List _propertyList;
    private int recordIndex;

    private bool IsEof
    {
        get
        {
            return (recordIndex>= _propertyList.Count);
        }
    }

    public PropertyMailMergeDataSource(List properties)
    {
        this._propertyList = properties;
        recordIndex = -1;
    }

    #region IMailMergeDataSource Members

    bool IMailMergeDataSource.GetValue(string fieldName, out object fieldValue)
    {
        var pInstruction = (_propertyList[recordIndex] as BasePropertyInstruction);

        fieldValue = null;

        try
        {
            switch (fieldName.ToUpper())
            {
                #region Property fields

                case "PASKINGPRICE":
                    fieldValue = pInstruction.AskingPrice;
                    return true;

                    ...
                    so on with mutiple
                case statements

                #endregion

                default:
                    fieldValue = "##FIELD DOES NOT EXIST##";
                    return true;
            }
        }
        catch (Exception ex)
        {
            fieldValue = "## FIELD: " + fieldName + " DOES NOT EXIST##";
            return true;
        }
    }

    bool IMailMergeDataSource.MoveNext()
    {
        if (!IsEof)
        {
            recordIndex++;
        }
        return (!IsEof);
    }

    string IMailMergeDataSource.TableName
    {
        get
        {
            return "Properties";
        }
    }

    public IMailMergeDataSource GetChildDataSource(string tableName)
    {
        var pInstruction = (_propertyList[recordIndex] as BasePropertyInstruction);

        switch (tableName)
        {
            case "Applicants":
                return new ApplicantsDataSource(pInstruction.Applicants.ToList());
            default:
                return null;
        }
    }

    #endregion
}

I have implemented IMailMergeDataSource again for ApplicantsDataSource similar as above with TableName as:

public string TableName
{
    get
    {
        return "Applicants";
    }
}

However when I am calling MailMerge.ExecuteWithRegions, it is throwing error after going over line in red color.

The error details are:

"Value cannot be null.\r\nParameter name: startParent"
"at Aspose.Words.CompositeNode.(Node, Node, Node, Node)
at Aspose.Words.CompositeNode.(Node, CompositeNode, Node, CompositeNode, Boolean) 
at Aspose.Words.Fields.Field.Remove()\r\n 
at (MailMerge, IMailMergeDataSource)\r\n at (ArrayList)\r\n 
at (MailMerge, IMailMergeDataSource)\r\n 
at Aspose.Words.Reporting.MailMerge.ExecuteWithRegions(IMailMergeDataSource dataSource)

Please help.

Hi

Thanks for your request. You have the same problem as described here:
https://forum.aspose.com/t/81390[
The reason of the problem is the default statement in your GetValue method, it should look like the following:

public bool GetValue(string fieldName, out object fieldValue)
{
    switch (fieldName)
    {
        case "Name":
            fieldValue = mProperties\[mRecordIndex\].Name;
            return true;
        default:
            fieldValue = null;
            return false;
    }
}

Best regards.](ExecuteWithRegions Value cannot be null. Parameter name: startParent)

You can use this class, it will work on any collection, and you will not have to implement a MailMerge datasource for every bussines entity.

///
/// Mail merge for a single or nested collection of objects
///
public class MailMergeObjectDatasource : IMailMergeDataSource
{
    private string _tableName;
    private IEnumerator _enumerator;

    ///
    /// Initializes a new instance of the class.
    ///
    /// The collection to enumerate.
    /// The name of the mail merge region as specified in the template document. Case-insensitive.
    public MailMergeObjectDatasource(IEnumerable collection, string tableName)
    {
        _enumerator = collection.GetEnumerator();
        _tableName = tableName;
    }

    #region IMailMergeDataSource Members
    ///
    /// The Aspose.Words mail merge engine invokes this method when it encounters a beginning of a nested mail merge region.
    ///
    /// The name of the mail merge region as specified in the template document. Case-insensitive.
    ///
    /// A data source object that will provide access to the data records of the specified table.
    ///
    ///
    ///
    /// When the Aspose.Words mail merge engines populates a mail merge region with data and encounters the beginning of a nested
    /// mail merge region in the form of MERGEFIELD TableStart:TableName, it invokes on the current
    /// data source object. Your implementation needs to return a new data source object that will provide access to the child
    /// records of the current parent record. Aspose.Words will use the returned data source to populate the nested mail merge region.
    ///
    ///
    /// Below are the rules that the implementation of must follow.
    ///
    ///
    /// If the table that is represented by this data source object has a related child (detail) table with the specified name,
    /// then your implementation needs to return a new object that will provide access
    /// to the child records of the current record.
    /// An example of this is Orders / OrderDetails relationship. Let's assume that the current object
    /// represents the Orders table and it has a current order record. Next, Aspose.Words encounters "MERGEFIELD TableStart:OrderDetails"
    /// in the document and invokes . You need to create and return a
    /// object that will allow Aspose.Words to access the OrderDetails record for the current order.
    ///
    ///
    /// If this data source object does not have a relation to the table with the specified name, then you need to return
    /// a object that will provide access to all records of the specified table.
    ///
    ///
    /// If a table with the specified name does not exist, your implementation should return null.
    ///
    ///
    public IMailMergeDataSource GetChildDataSource(string tableName)
    {
        object datasource;
        if (TryGetPropertyValue(tableName, out datasource) && datasource is IEnumerable)
            return new MailMergeObjectDatasource((IEnumerable)datasource, tableName);
        throw new InvalidOperationException("No property with the name " + tableName + " that implements IEnumerable was found in the current datasource");

    }

    ///
    /// Returns a value for the specified field name or false if the field is not found.
    ///
    /// The name of the data field.
    /// Returns the field value.
    /// True if value was found.
    public bool GetValue(string fieldName, out object fieldValue)
    {
        return TryGetPropertyValue(fieldName, out fieldValue) && (!(fieldValue is IEnumerable) || fieldValue is string); //Issue fix
    }

    ///
    /// Advances to the next record in the data source.
    ///
    ///
    /// True if moved to next record successfully. False if reached end of the data source.
    ///
    public bool MoveNext()
    {
        return _enumerator.MoveNext();
    }

    ///
    /// Returns the name of the data source.
    ///
    ///
    ///
    /// If you are implementing, return the name of the data 
    /// source from this property.
    /// Aspose.Words uses this name to match against the mail merge region name specified
    /// in the template document. The comparison between the data source name and
    /// the mail merge region name is not case sensitive.
    ///
    /// The name of the data source. Empty string if the data source has no name.
    public string TableName
    {
        get
        {
            return _tableName;
        }
    }
    #endregion
    
    ///
    /// Tries the get property value with the specified name.
    ///
    /// Name of the prop.
    /// The value.
    /// A value indecating of the property was found
    protected virtual bool TryGetPropertyValue(string propName, out object value)
    {
        var obj = _enumerator.Current;
        var property = obj.GetType().GetProperty(propName);
        if (property != null)
        {
            value = property.GetValue(obj, null);
            return true;
        }
        value = null;
        return false;
    }
}

Thanks Alexey,

But changing the switch - default case didn’t resolve the error.

As mentioned in the thread you pointed to, it’s a call to GetValue() even for child regions that causing the error.
So for the fix in my case, I have added following at the top of my GetValue() method

if (fieldName == "Applicants" || fieldName == "Vendors")
{
    fieldValue = null;
    return false;
}

Where “Applicants”, “Vendors” are table names for child regions in the document.

I think it should call GetValue() only for simple MergeFields and not for the ones having TableStart-TableEnd.

Thank you!

I also see this, but in my case the table name and field name might be the same sometimes so it’s not so easy to work around. Would it be possible to get this fixed?

Thanks

Hi Richard,

Thanks for your request. The problem is already resolved in the current codebase. The fix will be included into the next hotfix, which will be available in a week or two. You will be notified.
Best regards

The issues you have found earlier (filed as 12206) have been fixed in this update.

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