The concept of Text Control Reporting is to accept pre-shaped data. That means that Text Control's MailMerge component is merging the data rows 'as-is' and data shaping, sorting or queries are done in the application business layer.
Sometimes, it is required to define the order of data rows in the template. This sample shows how to realize this concept based on additional parameters in the merge block name and LINQ to sort the data rows.
Let's have a look at the XML data source. It consists of 2 simple tables: company and product and a relation between those tables.

The template simply consists of a single merge block with the name product. Without any sorting, the results would look like this:

In order to enable sorting, we simply introduce a new parameter to the merge block name:
[BlockName];orderby,[ColumnName],ASC|DESC
For example:
product;orderby,name,ASC
The sample code shows how to detect and parse these merge blocks in the template and how to replace the rows of the relevant DataTables with sorted DataRows.
The class DataSelector is parsing the template in order to sort the given data source. The property Template returns the changed template which is loaded into a MailMerge instance using LoadTemplateFromMemory. Finally, the document is merged with the sorted DataSet:
private string sDataSource = "data.xml"; | |
private string sTemplateFile = "template.tx"; | |
private void mergeToolStripMenuItem_Click(object sender, EventArgs e) | |
{ | |
// convert the XML file to a .NET DataSet | |
DataSet ds = new DataSet(); | |
ds.ReadXml(sDataSource, XmlReadMode.Auto); | |
// create a new DataSelector instance | |
DataSelector selector = new DataSelector(ds, | |
File.ReadAllBytes(sTemplateFile)); | |
// load the modified template | |
mailMerge1.LoadTemplateFromMemory(selector.Template, | |
TXTextControl.DocumentServer.FileFormat.InternalUnicodeFormat); | |
// merge the template with the new, sorted data source | |
mailMerge1.Merge(ds.Tables[0]); | |
// copy the merged document into a visible TextControl object | |
byte[] data = null; | |
mailMerge1.SaveDocumentToMemory(out data, | |
TXTextControl.BinaryStreamType.InternalUnicodeFormat, null); | |
textControl1.Load(data, | |
TXTextControl.BinaryStreamType.InternalUnicodeFormat); | |
} |
The DataSelector class loops through all merge blocks in a given template to check for specific keywords. A temporary ServerTextControl instance is used to recognize blocks with the sorting keyword 'ORDERBY'. If such tagged merge blocks are found, the parameters are parsed, stored and the block name is reset to the original block name.
Finally, LINQ is used to sort the data rows by the given column name. The resulting data rows are replaced in the specific DataTable. The DataTable won't be replaced completely to keep existing data relations and constraints.
// DataSelector | |
// description: This class loops through all merge blocks in a given template to check | |
// for sort keywords. The given referenced DataSet will be sorted. | |
// | |
// Parameters: dataSet of type DataSet, template as a byte[] array in the InternalUnicodeFormat | |
public class DataSelector | |
{ | |
public byte[] Template { get; set; } | |
public DataSelector(DataSet dataSet, byte[] template) | |
{ | |
// use a temporary ServerTextControl instance to recognize blocks with | |
// the sorting keyword 'ORDERBY' | |
using (TXTextControl.ServerTextControl tx = | |
new TXTextControl.ServerTextControl()) | |
{ | |
tx.Create(); | |
// load the template | |
tx.Load(template, TXTextControl.BinaryStreamType.InternalUnicodeFormat); | |
// create a list of merge blocks | |
List<MergeBlock> lMergeBlocks = MergeBlock.GetMergeBlocks(MergeBlock.GetBlockMarkersOrdered(tx), tx); | |
// loop through all merge blocks to check whether they | |
// have a sorting parameter | |
foreach (MergeBlock mergeBlock in lMergeBlocks) | |
{ | |
string sBlockName = mergeBlock.StartMarker.TargetName; | |
// check for the unique sorting parameter | |
if (sBlockName.ToUpper().Contains("ORDERBY") == false) | |
continue; | |
// create a new SortedBlock object to store parameter | |
SortedBlock block = new SortedBlock(sBlockName); | |
// remove the sorting parameter from the block name | |
// so that MailMerge can find the matching data in the data source | |
mergeBlock.StartMarker.TargetName = block.Name; | |
mergeBlock.EndMarker.TargetName = "BlockEnd_" + mergeBlock.Name; | |
if (dataSet.Tables.Contains(mergeBlock.Name) == false) | |
continue; | |
// get all DataRows using LINQ | |
var query = from product in dataSet.Tables[mergeBlock.Name].AsEnumerable() | |
select product; | |
// create a new DataTable with the sorted DataRows | |
DataTable dtBoundTable = (block.SortType == SortType.ASC) ? | |
query.OrderBy(product => product.Field<String>(block.ColumnName)).CopyToDataTable() : | |
query.OrderByDescending(product => product.Field<String>(block.ColumnName)).CopyToDataTable(); | |
// remove original rows and replace with sorted rows | |
dataSet.Tables[mergeBlock.Name].Rows.Clear(); | |
dataSet.Tables[mergeBlock.Name].Merge(dtBoundTable); | |
dtBoundTable.Dispose(); | |
} | |
// save the template | |
byte[] data = null; | |
tx.Save(out data, TXTextControl.BinaryStreamType.InternalUnicodeFormat); | |
this.Template = data; | |
} | |
} | |
} |
Let's do two examples. The merge block name contains the following string:
product;orderby,name,ASC
The ascending sorted results are shown below:

Descending sorting results:

LINQ is used to sort the results to show the flexibility. You can customize this process with your own selectors. This shows the flexibility and the power of the MailMerge class. Give it a try to download the sample project directly from GitHub.
Download the sample from GitHub and test it on your own.