BizTalk: Group Mapping Using LINQ and Objects in Helper Classes: A Comparison
Introduction
In this article we will compare and contrast different ways of doing group based transformations in BizTalk. We will consider following ways
- Using XML Linq to Create the Response
- Using Object Oriented Principles to Create the Response.
Problem
We want to map the following request (fig 1) to the response (fig 2) where the policy records in response are to be created by collecting data from request based on policy Id. The request and response schemas are as follows
Figure 1: Request Schema
Figure 2: Response Schema
The data from each of the Customer, Proposal, Policy and Payment sections need to be combined together in response under individual records based upon the policy.
Solution
We will explore the solution to above problem using three different approaches mentioned in the introduction.
Using XML LINQ
In this approach we convert the BizTalk message to a XDocument in an helper class. Then based upon Lambda expressions and Xpaths we read data from the request and then create the response XDocument. This object is then serialized back into the BizTalk message and then we pass back the message to the calling orchestration. We can convert the BizTalk message to the XDocument class using following code.
public static bool PerformXDocTransform(XLANGMessage msgPolicyDetailsRequest, XLANGMessage msgPolicyDetailsResponse)
{
bool status = false;
XDocument xdocPolicyDetailsRequest = XDocument.Load((Stream)msgPolicyDetailsRequest[0].RetrieveAs(typeof(Stream)));
XDocument xdocPolicyDetailsResponse = XDocument.Load((Stream)msgPolicyDetailsResponse[0].RetrieveAs(typeof(Stream)));
xdocPolicyDetailsResponse = PerformXDocTransform(xdocPolicyDetailsRequest, xdocPolicyDetailsResponse);
if (xdocPolicyDetailsResponse != null)
status = true;
var btStream = new VirtualStream();
xdocPolicyDetailsResponse.Save(btStream);
msgPolicyDetailsResponse[0].LoadFrom(btStream);
return status;
}
This method invokes another overloaded version of the same method PerformXDocTransform method which accepts the XDocument for request and the response and returns back a modified response XDocument which then seriraliezed to BizTalk message in above method. The Overloaded method is as follows.
public static XDocument PerformXDocTransform(XDocument xdocPolicyDetailsRequest, XDocument xdocPolicyDetailsResponse)
{
XNamespace responseNs = "http://BizTalkGroupMappingDemo.Schemas.PolicyDetailsResponse";
IEnumerable<XElement> policies = xdocPolicyDetailsRequest.Descendants("Policy");
foreach (var policy in policies)
{
string policyId = policy.Element("PolicyId") == null ? string.Empty : policy.Element("PolicyId").Value;
if (!string.IsNullOrEmpty(policyId))
{
XElement xCustomer = xdocPolicyDetailsRequest.Descendants("Customer").Where(x => x.Element("PolicyId").Value == policyId).FirstOrDefault();
XElement xProposal = xdocPolicyDetailsRequest.Descendants("Proposal").Where(x => x.Element("PolicyId").Value == policyId).FirstOrDefault();
XElement xPayment = xdocPolicyDetailsRequest.Descendants("Payment").Where(x => x.Element("PolicyId").Value == policyId).FirstOrDefault();
XElement individualPolicy = new XElement("Policy",
new XElement("PolicyId", policyId),
new XElement("CustomerId", xCustomer.Element("CustomerId").Value),
new XElement("Name", (xCustomer.Element("FirstName").Value + " " + xCustomer.Element("LastName").Value)),
new XElement("Quote", xProposal.Element("QuoteAmt").Value),
new XElement("Premium", xProposal.Element("Premium").Value),
new XElement("ProposalId", xProposal.Element("ProposalId").Value),
new XElement("StartDate", policy.Element("StartDate").Value),
new XElement("EndDate", policy.Element("EndDate").Value),
new XElement("PaymentId", xPayment.Element("PaymentId").Value));
xdocPolicyDetailsResponse.Element(responseNs + "IndividualPolicyRecords").Add(individualPolicy);
}
}
return xdocPolicyDetailsResponse;
}
The above method can be invoked from a BizTalk orchestration form a construct shape as shown below. the message msgPolicyDetailsXDocResponse is constructed on the constructed shape.
varXmlDoc = new System.Xml.XmlDocument();
varXmlDoc.LoadXml("<ns0:IndividualPolicyRecords xmlns:ns0='http://BizTalkGroupMappingDemo.Schemas.PolicyDetailsResponse'></ns0:IndividualPolicyRecords>");
msgPolicyDetailsXDocResponse = varXmlDoc;
//msgPolicyDetailsRequest is input message to orchestration and
//msgPolicyDetailsXDocResponse is output message sent out from orchestration
varXDocTransaformStatus = BizTalkGroupMappingDemo.Components.OrchestrationHelper.PerformXDocTransform(msgPolicyDetailsRequest, msgPolicyDetailsXDocResponse);
When above piece of code is executed, the mapping will be done out of the BizTalk orchestration and in a helper class. Generally we should use non transnational scopes where ever we can while we are calling the static helper methods, in that way the size of the state persisted to the database will be minimal.
Sample Test
Following request was submitted to the sample orchestration which consumed the above static helper method.
<ns0:GroupPolicyRecords xmlns:ns0="http://BizTalkGroupMappingDemo.Schemas.PolicyDetails">
<Customers>
<Customer>
<CustomerId>Cust10001</CustomerId>
<FirstName>Mandar</FirstName>
<LastName>Dharmadhikari</LastName>
<PolicyId>Pol10001</PolicyId>
</Customer>
<Customer>
<CustomerId>Cust10002</CustomerId>
<FirstName>James</FirstName>
<LastName>Wong</LastName>
<PolicyId>Pol10002</PolicyId>
</Customer>
</Customers>
<Proposals>
<Proposal>
<QuoteAmt>1000</QuoteAmt>
<AllocatedAmount>1000</AllocatedAmount>
<Premium>10</Premium>
<ProposalId>Prop10001</ProposalId>
<PolicyId>Pol10001</PolicyId>
</Proposal>
<Proposal>
<QuoteAmt>2000</QuoteAmt>
<AllocatedAmount>1500</AllocatedAmount>
<Premium>15</Premium>
<ProposalId>Prop10002</ProposalId>
<PolicyId>Pol10002</PolicyId>
</Proposal>
</Proposals>
<Policies>
<Policy>
<StartDate>1/11/2018</StartDate>
<EndDate>1/10/2019</EndDate>
<PolicyId>Pol10001</PolicyId>
</Policy>
<Policy>
<StartDate>30/10/2018</StartDate>
<EndDate>30/09/2018</EndDate>
<PolicyId>Pol10002</PolicyId>
</Policy>
</Policies>
<Payments>
<Payment>
<isMonthlyPayment>yes</isMonthlyPayment>
<PaymentId>Pay10001</PaymentId>
<PolicyId>Pol10001</PolicyId>
</Payment>
<Payment>
<isMonthlyPayment>yes</isMonthlyPayment>
<PaymentId>Pay10002</PaymentId>
<PolicyId>Pol10002</PolicyId>
</Payment>
</Payments>
</ns0:GroupPolicyRecords>
Following response was obtained.
<?xml version="1.0" encoding="utf-8"?>
<ns0:IndividualPolicyRecords xmlns:ns0="http://BizTalkGroupMappingDemo.Schemas.PolicyDetailsResponse">
<Policy>
<PolicyId>Pol10001</PolicyId>
<CustomerId>Cust10001</CustomerId>
<Name>Mandar Dharmadhikari</Name>
<Quote>1000</Quote>
<Premium>10</Premium>
<ProposalId>Prop10001</ProposalId>
<StartDate>1/11/2018</StartDate>
<EndDate>1/10/2019</EndDate>
<PaymentId>Pay10001</PaymentId>
</Policy>
<Policy>
<PolicyId>Pol10002</PolicyId>
<CustomerId>Cust10002</CustomerId>
<Name>James Wong</Name>
<Quote>2000</Quote>
<Premium>15</Premium>
<ProposalId>Prop10002</ProposalId>
<StartDate>30/10/2018</StartDate>
<EndDate>30/09/2018</EndDate>
<PaymentId>Pay10002</PaymentId>
</Policy>
</ns0:IndividualPolicyRecords>
Above test result shows that the transformation happened successfully using the helper class.
Using Object Oriented Programming With Schema Objects
In this approach we use the principles of Object Oriented Programming to manipulate the values in the response by creating the objects from the BizTalk messages. In this approach, we need to create the classes from the request and response schemas. We store the classes in a helper project and write our methods around the request and response objects. In order to generate the classes from the request and response xsds, we can use the xsd.exe command in the pre build/post build events of our Visual Studio project. Refer a sample below.
Once the Project containing the schemas is built, it generates the classes in the Components Project under the XSDClasses folder as shown below.
Once this is done we can write the logic to serialize and deserialize the BizTalk message from and to the objects of the classes that we just created. The code to do can be on following lines.
public static bool PerformOOPTransform(XLANGMessage msgPolicyDetailsRequest, XLANGMessage msgPolicyDetailsResponse)
{
bool status = false;
var btStream = (Stream)msgPolicyDetailsRequest[0].RetrieveAs(typeof(Stream));
Request.GroupPolicyRecords groupPolicyRecords = null;
Response.IndividualPolicyRecords individualPolicyRecords = null;
XmlSerializer reqSerializer = new XmlSerializer(typeof(Request.GroupPolicyRecords));
XmlSerializer respSerializer = new XmlSerializer(typeof(Response.IndividualPolicyRecords));
try
{
groupPolicyRecords = (Request.GroupPolicyRecords)reqSerializer.Deserialize(btStream);
}
catch (Exception ex)
{
throw new Exception("Failed To Derserialize the BizTalk msg", ex);
}
individualPolicyRecords = PerformOOPTransform(groupPolicyRecords, individualPolicyRecords, out status);
btStream = new VirtualStream();
respSerializer.Serialize(btStream, individualPolicyRecords);
msgPolicyDetailsResponse[0].LoadFrom(btStream);
return status;
}
Above method invokes the overloaded method by same name which accepts objects instead of the BizTalk message and this overloaded method creates the necessary objects for the response.
private static Response.IndividualPolicyRecords PerformOOPTransform(Request.GroupPolicyRecords req, Response.IndividualPolicyRecords resp, out bool status)
{
resp = new Response.IndividualPolicyRecords();
List<Response.IndividualPolicyRecordsPolicy> policyList = new List<Response.IndividualPolicyRecordsPolicy>();
foreach (Request.GroupPolicyRecordsPolicy policy in req.Policies)
{
string policyId = policy.PolicyId;
Request.GroupPolicyRecordsCustomer customer = req.Customers.Where(x => x.PolicyId == policyId).FirstOrDefault();
Request.GroupPolicyRecordsProposal proposal = req.Proposals.Where(x => x.PolicyId == policyId).FirstOrDefault();
Request.GroupPolicyRecordsPayment payment = req.Payments.Where(x => x.PolicyId == policyId).FirstOrDefault();
if (customer == null || proposal == null || payment == null)
{
status = false;
return resp;
}
Response.IndividualPolicyRecordsPolicy individualPolicy = new Response.IndividualPolicyRecordsPolicy(customer, proposal, payment, policy);
individualPolicy.UpdateFullName();
policyList.Add(individualPolicy);
}
Response.IndividualPolicyRecordsPolicy[] policyArray = policyList.ToArray<Response.IndividualPolicyRecordsPolicy>();
resp.Policy = policyArray;
status = true;
return resp;
}
Now as sticking to the OOP principles, we can write the methods in a class which work on the object of that particular class. For example, in above method, the Constructor for IndivdualPolicyRecords creates a response Policy object, then calls the UpdateFullName. These methods can be written in partial classes as shown below.
public partial class IndividualPolicyRecordsPolicy
{
private string _firstName = string.Empty;
private string _lastName = string.Empty;
GroupPolicyRecordsCustomer _customer = null;
public IndividualPolicyRecordsPolicy() { }
public IndividualPolicyRecordsPolicy(GroupPolicyRecordsCustomer customer,
GroupPolicyRecordsProposal proposal,
GroupPolicyRecordsPayment payment,
GroupPolicyRecordsPolicy policy)
{
this._customer = customer;
Name = string.Empty;
CustomerId = customer.CustomerId;
Quote = proposal.QuoteAmt;
Premium = proposal.Premium;
ProposalId = proposal.ProposalId;
PaymentId = payment.PaymentId;
StartDate = policy.StartDate;
EndDate = policy.EndDate;
PolicyId = policy.PolicyId;
}
/// <summary>
/// Method To Update Full Name
/// </summary>
/// <returns>Full Name</returns>
public void UpdateFullName()
{
this.nameField = _customer.FirstName + " " + _customer.LastName;
}
}
We can invoke the PerformOOPTransform method from BizTalk orchestration just like we did with previous one.
Test Result
Following request was submitted to the sample orchestration which consumed the above static helper method.
<ns0:GroupPolicyRecords xmlns:ns0="http://BizTalkGroupMappingDemo.Schemas.PolicyDetails">
<Customers>
<Customer>
<CustomerId>Cust10001</CustomerId>
<FirstName>Mandar</FirstName>
<LastName>Dharmadhikari</LastName>
<PolicyId>Pol10001</PolicyId>
</Customer>
<Customer>
<CustomerId>Cust10002</CustomerId>
<FirstName>James</FirstName>
<LastName>Wong</LastName>
<PolicyId>Pol10002</PolicyId>
</Customer>
</Customers>
<Proposals>
<Proposal>
<QuoteAmt>1000</QuoteAmt>
<AllocatedAmount>1000</AllocatedAmount>
<Premium>10</Premium>
<ProposalId>Prop10001</ProposalId>
<PolicyId>Pol10001</PolicyId>
</Proposal>
<Proposal>
<QuoteAmt>2000</QuoteAmt>
<AllocatedAmount>1500</AllocatedAmount>
<Premium>15</Premium>
<ProposalId>Prop10002</ProposalId>
<PolicyId>Pol10002</PolicyId>
</Proposal>
</Proposals>
<Policies>
<Policy>
<StartDate>1/11/2018</StartDate>
<EndDate>1/10/2019</EndDate>
<PolicyId>Pol10001</PolicyId>
</Policy>
<Policy>
<StartDate>30/10/2018</StartDate>
<EndDate>30/09/2018</EndDate>
<PolicyId>Pol10002</PolicyId>
</Policy>
</Policies>
<Payments>
<Payment>
<isMonthlyPayment>yes</isMonthlyPayment>
<PaymentId>Pay10001</PaymentId>
<PolicyId>Pol10001</PolicyId>
</Payment>
<Payment>
<isMonthlyPayment>yes</isMonthlyPayment>
<PaymentId>Pay10002</PaymentId>
<PolicyId>Pol10002</PolicyId>
</Payment>
</Payments>
</ns0:GroupPolicyRecords>
Following response was obtained.
<IndividualPolicyRecords xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://BizTalkGroupMappingDemo.Schemas.PolicyDetailsResponse">
<Policy xmlns="">
<PolicyId>Pol10001</PolicyId>
<CustomerId>Cust10001</CustomerId>
<Name>Mandar Dharmadhikari</Name>
<Quote>1000</Quote>
<Premium>10</Premium>
<ProposalId>Prop10001</ProposalId>
<StartDate>1/11/2018</StartDate>
<EndDate>1/10/2019</EndDate>
<PaymentId>Pay10001</PaymentId>
</Policy>
<Policy xmlns="">
<PolicyId>Pol10002</PolicyId>
<CustomerId>Cust10002</CustomerId>
<Name>James Wong</Name>
<Quote>2000</Quote>
<Premium>15</Premium>
<ProposalId>Prop10002</ProposalId>
<StartDate>30/10/2018</StartDate>
<EndDate>30/09/2018</EndDate>
<PaymentId>Pay10002</PaymentId>
</Policy>
</IndividualPolicyRecords>
Observations
Let us compare the two approaches that we discussed.
- When we use the LINQ with XDocument class, we have to write a code in a procedural style of prgramming where we create methods which tend to a certain function.
- When we use the OOP concepts with the objects created from the schemas, we see that we can exploit the OOP concepts to sgeggregate the work based upon objects, this gives us more granualar control on what a particular class does and how and what data it process.
- There is actually a third way of processing the request that we have, we can use external xslt or inline xslt with the BizTalk map to create the necessary mapping. This approach can use the Muench mapping based of the keys in xslt and can speed up the transformation. This approach is again a procedural approach where we define templates which perform each of sub mapping tasks.
- In case of using Helper Classes for mapping be it using XML LINQ or using objects, we get a advantage of doing modular deployments, we can just GAC the helper class assembly, restart the host instance and the mapping changes should be picked up.
- If we use the map with etxernal of inline xslt/templates, we need to do the deployment of maps dll which always mostly is referred by the orchestrations, hence we require a proper complete deployment.
Conclusion
From above observations, we can conclude following
- If the mapping is group based and is fairly simple like doing sum from all the records based upon a certain id, then easiest way is to use a inline xslt or external xslt to achieve the mapping. The ease of mapping trumps the need for redeployment here and saves us the maintenance of the helper class code.
- If the mapping is easy to medium complex and involves some logic for mapping data (example to refer BRE for a transformation codes etc) then using LINQ maybe considered a better approach as it gives us a lot of control over the code.
- In case the transformation is very logic heavy, where we need to do lot of data mappings, move over the data again and again mutiple times, then in such case it becomes very easy to convert the request and response messages into and from objects. Plus it provides us with a tight control on the code and we can manage the code in a better way.
See Also
Following articles can be read for extra learning.
- XSLT Muenchian Grouping - BizTalk Complex Transformation
- BizTalk Training – Mapping – How to implement multi-level Muenchian grouping in BizTalk Maps
References
Following articles were referred while writing this article.
- BizTalk Server 2010: .NET Helper Classes
- BizTalk Training – Mapping – How to implement multi-level Muenchian grouping in BizTalk Maps