Share via


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

  1. Using XML Linq to Create the Response
  2. Using Object Oriented Principles to Create the Response.

↑Back To Top


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.

↑Back To Top 


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>

↑Back To Top


Observations

Let us compare the two approaches that we discussed.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

↑Back To Top


Conclusion

From above observations, we can conclude following

  1. 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.
  2. 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.
  3. 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.

↑Back To Top


See Also

Following articles can be read for extra learning.

↑Back To Top


References

Following articles were referred while writing this article.

  1. BizTalk Server 2010: .NET Helper Classes
  2. BizTalk Training – Mapping – How to implement multi-level Muenchian grouping in BizTalk Maps

↑Back To Top