Quick overview - mamift/LinqToXsdCore GitHub Wiki

The following details a quick technical overview of what LinqToXsd provides to a programmer dealing with XML that has accompanying XSD.

Sample XML data

Let’s look at some XML instance data on which to carry out XML programming. The sample given below comprises a batch of purchase orders of a very simple form.

<Batch xmlns="http://www.example.com/Orders">
  <PurchaseOrder>
    <CustId>0815</CustId>
    <Item>
      <ProdId>1234</ProdId>
      <Price>37</Price>
      <Quantity>2</Quantity>
    </Item>
    <Item>
      <ProdId>5678</ProdId>
      <Price>1.5</Price>
      <Quantity>3</Quantity>
    </Item>
  </PurchaseOrder>
  <PurchaseOrder>
    <CustId>1324</CustId>
    <Item>
      <ProdId>7788</ProdId>
      <Price>42</Price>
      <Quantity>1</Quantity>
    </Item>
  </PurchaseOrder>
</Batch>

Sample untyped XML program

The following LINQ to XML code totals all items of orders.

static double CalculateTotal(XElement batch) 
{
  XNamespace ns = "http://www.example.com/Orders";
  return
    (from purchaseOrder in batch.Elements(ns + "PurchaseOrder")
     from item in purchaseOrder.Elements(ns + "Item")
     select (double)item.Element(ns + "Price")
          * (int)item.Element(ns + "Quantity")
    ).Sum();
}

Explanation:

  • The function locally defines a namespace object, ns, which is reused in the query.
  • Element names are encoded by strings as passed to Element and Elements.
  • The LINQ query iterates over all orders and all their items; see the syntax from … from ….
  • For each item, price and quantity values are extracted subject to some casts.
  • The query result (an IEnumerable) is aggregated with the Sum function.

Sample XML schema

The following XML schema (XSD) defines the schema contract for purchase orders.

<xs:schema
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  targetNamespace="http://www.example.com/Orders"
  xmlns="http://www.example.com/Orders"
  elementFormDefault="qualified">

  <xs:element name="Batch">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="PurchaseOrder"
                    minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  
  <xs:element name="PurchaseOrder">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="CustId" type="xs:string"/>
        <xs:element ref="Item"
                    minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:element name="Item">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="ProdId" type="xs:string"/>
        <xs:element name="Price" type="xs:double"/>
        <xs:element name="Quantity" type="xs:int"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  
</xs:schema>

Sample typed XML program

LINQ to XSD maps the above schema readily to a CLR namespace. The child axis can be explored in a typed fashion now. Hence, the LINQ to XSD code for totaling all items of orders reads as follows:

using www.example.com.Orders;

static double CalculateTotal(Batch batch) 
{
    return
      (from purchaseOrder in batch.PurchaseOrder
       from item in purchaseOrder.Item
       select item.Price * item.Quantity
      ).Sum();
}

Some observations:

  • The ‘shape’ of the untyped code is preserved.
  • In particular, the pattern of using LINQ carries over.
  • All string-encoded access in the untyped code is eliminated in the typed code.
  • All casts in the untyped code is eliminated in the typed code.
  • XML namespaces are replaced by CLR namespaces.

Sample XML object types

The LINQ to XSD programmer codes against classes that are automatically generated from an XML schema. These classes (say, XML object types) provide typed views on LINQ to XML elements. The classes comprise members as follows:

  • A default constructor.
  • Properties for the typed child axis and attributes.
  • Infrastructural members: Load, Save and Clone.
  • Inherited members for query axes (Descendants and Co.). There is also an explicit coercion (cast) to construct a typed wrapper from an XElement instance. Such a cast is useful when ‘untyped’ XML trees readily exist in an program scope, and a typed view should be created. Such a cast is not needed when `typed’ XML trees are readily loaded into instances of the generated classes. The API documentation discusses this subject in more detail.

The object model for the running example looks as follows in pseudo code: (See the mapping documentation for an explanation of the format below.)

class Batch: XTypedElement {
  constructor Batch();
  explicit cast Batch(XElement xe);
  property IList < PurchaseOrder > PurchaseOrder { get; set; }
}
class PurchaseOrder: XTypedElement {
  constructor PurchaseOrder();
  explicit cast PurchaseOrder(XElement xe);
  property string CustId { get; set; }
  property IList < Item > Item { get; set; }
}
class Item: XTypedElement {
  constructor Item();
  explicit cast Item(XElement xe);
  property string ProdId { get; set; }
  property double Price { get; set; }
  property int Quantity { get; set; }
}

Sample code to run

For the reader’s convenience, here is a self-contained sample program with a Main function.

using System;
using System.Query;
using www.example.com.Orders;

public class Program 
{
  static double CalculateTotal(Batch batch) 
  {
    return
      (from purchaseOrder in batch.PurchaseOrder
       from item in purchaseOrder.Item
       select item.Price * item.Quantity
      ).Sum();
  }

  public static void Main()
  {
    // Load an element with orders
    var os = Batch.Load("../../XMLFile1.xml");

    // Calculuate and print the total
    var total = CalculateTotal(os);
    Console.WriteLine(total);
  }
}
⚠️ **GitHub.com Fallback** ⚠️