Manual - mamift/LinqToXsdCore GitHub Wiki

1. Introduction

This manual covers some topics of LINQ to XSD that a user may need to know. Note that it was written back in 2008, so there may be parts of it that are outdated, however as far as I can tell, almost all of it is still valid for this .NET Core port of the project.

Section 2 of this manual describes the API of the classes that LINQ to XSD generates from XML schemas.

Section 3 of this manual describes different means of customizing class generation and the generated classes.

Section 4 of this manual discusses topics regarding application development (such as build issues).

2. The API of XML Objects

2.1 Construction (new)

Essentially, a class for an XSD type is instantiated by the use of a default constructor. However, there are a few exceptions and special cases that are identified below. Several of the details that follow are also discussed in the mapping documentation for LINQ to XSD.

Default constructors

The types in running example of the LINQ to XSD overview use the following constructors:

public Batch();
public PurchaseOrder();
public Item();

Unary element constructors

Element declarations of a named type are mapped to classes with a single-argument constructor such that the content of the element can be readily provided at element-construction time. Consider the following schema fragment that declares two roots of type Address:

  <xs:element name="BillTo" type="Address"/>
  <xs:element name="ShipTo" type="Address"/>
  <xs:complexType name="Address">
    <xs:sequence>
      <!-- ... -->
    </xs:sequence>
  </xs:complexType>

For instance, the root-element declaration BillTo implies the following constructor:

public BillTo(Address content);

The content of such an object (corresponding to an element declaration of a named type) can always be extracted by a designated Content property. For instance, the class BillTo provides the following Content property:

public Address Content { get; }

When a class for an element declaration with a named XSD type is instantiated by its default constructor, then the content object is automatically created. It is worth mentioning that the provision of the single-argument constructor is more than a convenience once the underlying element declaration is of a named complex type that serves as the base type for XSD-type derivation. In such a case, the use of the single-argument constructor is essential for the construction of typed XML trees with contents of a derived XSD type.

Abstract types and elements

Abstract complex type-definitions and root-element declarations are mapped to abstract classes.

For instance, consider this head of a substitution group for different kinds of messages in a web service:

<xs:element name="Message" type="MessageType" abstract="true"/>
<xs:complexType name="MessageType" abstract="true"/>

The following classes are generated:

public abstract class Message : XTypedElement {}
public abstract class MessageType : XTypedElement {}

Note: The current release of LINQ to XSD provides incomplete support for abstract classes with regard to their use in casts. Also, there are unresolved problems with edge cases that involve subtyping, even for the case of a type hierarchy that only involves concrete classes.

2.2 Load

The Load methods of generated classes are typed versions of LINQ to XML’s Load methods for XElement. That is, these methods create an instantiate a generated class; the instance serves as a typed view on an XElement instance which in turn is created from a data source containing raw XML. That is, untyped loading is completed into typed loading by casting the untyped loading result (an instance of type XElement) to the type that is requested in the static method call. The LINQ to XML documentation explains the various forms of loading in more detail.

public abstract class xyz : XTypedElement
{
   ...

   // A URI string referencing the file to load
   public static xyz Load(string uri);

   // A variation that admits control of whitespace preservation
   public static xyz Load(string uri, bool preserveWhitespace);

   // Use a text reader as the data source
   public static xyz Load(TextReader textReader);

   // A variation that admits control of whitespace preservation
   public static xyz Load(TextReader textReader, bool preserveWhitespace);

   // Use an XML reader as the data source
   public static xyz Load(XmlReader reader);

}

Note: The current release of LINQ to XSD may differ with regard to actual overloads for Load.

2.3 Parse

The Parse methods of generated classes are typed versions of LINQ to XML’s Parse methods for XElement. The method parses a string containing XML into an XElement instance and casts that instance to the type requested in the static method call. Optionally whitespace can be preserved. The XML must contain only one root node.

public abstract class xyz : XTypedElement
{
   ...

   public static XElement Parse(string text);
   public static XElement Parse(string text, bool preserveWhitespace);

}

Note: The current release of LINQ to XSD may differ with regard to actual overloads for Parse.

2.4 Save

The Save methods of generated classes are typed versions of LINQ to XML’s Save methods for XElement. These methods output the underlying XML tree for the typed wrapper at hand. In fact, Save is simply forwarded to the associated XElement instance. The output can be saved to a file, a TextWriter, or an XmlWriter. Optionally whitespace can be preserved.

public abstract class xyz : XTypedElement
{
   ...

   public void Save(string fileName);
   public void Save(string fileName, bool preserveWhitespace);
   public void Save(TextWriter textWriter);
   public void Save(TextWriter textWriter, bool preserveWhitespace);
   public void Save(XmlWriter writer);
}

Note: The current release of LINQ to XSD may differ with regard to actual overloads for Save.

2.5 Cast (from XElement)

Note: This topic is undocumented for the current release of LINQ to XSD.

2.6 XElement access

Instances of the classes that are generated by LINQ to XSD keep a handle to an XElement instance for the (untyped) XML tree underneath. The base class of all generated LINQ to XSD classes, XTypedElement, provides the Untyped property for direct access to this XElement instance. The following example illustrates the Untyped property for the purpose of using the untyped descendant axis on typed XML trees; the number of managers in a company are counted.

static int CountManagers(Company c)
{
    XNamespace ns = "http://www.example.com/Company";
    return c.Untyped.Descendants(ns + "Manager").Count();
}

2.7 Descendants & friends

The classes generated by LINQ to XSD provide extra query axes (in addition to the straightforward child/attribute axes). That is, there are type-driven descendant and ancestor axes. In terms of the API, the corresponding members are grouped under the Query property of the generated classes. (Such grouping makes the intellisense experience more usable.) The following examples show an invocation of the descendant axis; the number of employees in a given company are counted.

static int CountHeads(Company c)
{
    return c.Query.Descendants<EmployeeType>().Count();
}

The additional query axes are enumerated by the following interface:

public interface IXTyped
{
    IEnumerable<T> Descendants<T>()        where T : XTypedElement;
    IEnumerable<T> Ancestors<T>()          where T : XTypedElement;
    IEnumerable<T> SelfAndDescendants<T>() where T : XTypedElement;
    IEnumerable<T> SelfAndAncestors<T>()   where T : XTypedElement;
}

The base of all generated classes, XTypedElement, implements this interface.

public partial class XTypedElement : IXTyped 
{
    public IXTyped Query {
        get {
            return (IXTyped)this;
        }
    }    
}

Note: The current release of LINQ to XSD is limited with regard to the use of abstract types (as opposed to concrete types) for the type parameters of the additional query axes. This may imply that the root type of a derivation hierarchy, when it is abstract, cannot be used effectively in queries.

2.8 Clone

The base class of all generated classes, XTypedElement, defines a Clone method. This is a deep clone in the sense that the underlying untyped XML tree is cloned. As usual, the result of the clone operation is weakly typed. Hence, a cast must be used to recover the intended type:

var po1 = PurchaseOrder.Load("../../Xml/po1.xml");
var po2 = (PurchaseOrder)po1.Clone();

In fact, there is hardly any incentive to clone explicitly. Instead it is important that the LINQ to XSD programmer (as much as the LINQ to XML) programmer is aware of implicit cloning. That is, when an XML object is assigned to multiple subtree positions, then it is actually cloned as part of the assignment (say, in the process of ‘parenting’). Consider the following code that constructs a purchase order and assigns the same address to BillTo and ShipTo:

var po1 = new PurchaseOrder();
var adr = new USAddress();
adr.Country = "US";
adr.City = "Mercer Island";
adr.Name = "Patrick Hines";
adr.State = "WA";
adr.Street = "123 Main St";
adr.Zip = 68042;
po1.BillTo = adr;
po1.ShipTo = adr;

When the fresh USAddress adr is assigned for the first time to po1.BillTo, then it gets parent. Hence, the subsequent assignment to po1.ShipTo implies that the original address is cloned and a clone is assigned to po1.ShipTo. The contents of the variable adr remains unchanged.

In different terms, the following conditions hold:

Trace.Assert(po1.BillTo.Equals(adr));
Trace.Assert(!po1.BillTo.Equals(po1.ShipTo));

2.9 Default values

Defaults do not affect the mere type of API members. Instead, they only affect the behavior of getters for properties that implement declarations for attributes or elements with defaults. In the case of attributes, whenever the corresponding attribute is not found in the XML tree, then the corresponding getter returns the default value. In the case of elements, whenever the content of the corresponding element in the XML tree is empty, then the corresponding getter returns the default value.

Note: The current release of LINQ to XSD provides designated support for default attributes, but the defaults for elements are not taken into account in any way.

Consider the following XSD fragment with an attribute declaration that defines a default:

  <xs:complexType name="IntlStreetAddress">
    <xs:sequence>
      <xs:element name="Street" type="xs:string"/>
      <xs:element name="City" type="xs:string"/>
      <xs:element name="Zip" type="xs:int"/>
      <xs:element name="State" type="xs:string"/>
    </xs:sequence>
    <xs:attribute name="Country" type="xs:string" default="US"/>
  </xs:complexType>

Now consider the following code that constructs an international address. Note that there is no assignment to the Country property.

var adr = new IntlStreetAddress {
    Street = "123 Main St",
    City = "Mercer Island",
    Zip = 68042,
    State = "WA"
};

The default defined by the XML schema kicks in when we invoke the getter for the Country property. Hence, the following condition succeeds, when tested right after the above construction sequence:

// Test for default for country
Trace.Assert(adr.Country == "US");

2.10 Wildcards

A generated class that implements a content model with an element wildcard provides a member Any. Likewise, a content model with an attribute wildcard gives rise to a member AnyAttribute. These members are properties of type IEnumerable or IEnumerable, resp.

Note: The current release of LINQ to XSD does not support attribute wildcards. Also, the property Any currently only provides a getter.

For instance, consider the following schema:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
  targetNamespace="http://LinqToXsdSamples/Schemas/Wild"
  xmlns="http://LinqToXsdSamples/Schemas/Wild"
  elementFormDefault="qualified">
  <xs:element name="Product">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Number" type="xs:string"/>
        <xs:element name="Name" type="xs:string"/>
        <xs:element name="Size" type="xs:string"/>
        <xs:any minOccurs="0" maxOccurs="unbounded"
                namespace="##other" processContents="lax"/>
      </xs:sequence>
      <xs:anyAttribute namespace="##other" processContents="skip"/>
    </xs:complexType>
  </xs:element>
</xs:schema>

We can load the instance and access the wildcard as follows:

var p = Product.Load(...);
var w = p.Any.First();

3. Customization of XML objects

3.1 Mapping-time customization

LINQ to XSD deliberately limits the degree of mapping-time customization:

  • LINQ to XSD focuses on canonical as opposed to free-wheeling X-to-O mapping.
  • The canonical mapping is meant to apply to all possible schemas.

LINQ to XSD can take into account a configuration file that may control the following mapping details:

  • Mapping of XML namespaces to CLR namespaces.
  • Systematic conversion of nested, anonymous complex types into complex-type definitions.

The characteristics of the configuration files are these:

  • Configurations are specified in an XML format with a designated namespace.
  • The command line processor of LINQ to XSD offers an option for the configuration file.
  • A LINQ to XSD VS project may designate a configuration file. Please, use the build action ‘LinqToXsdConfiguration’.

Mapping namespaces The following sample maps http://www.example.com/PO1 to LinqToXsdSamples.Schemas.PO1. The default mapping for http://www.example.com/PO1 would have been www.example.com.PO1.

<Configuration xmlns="http://www.microsoft.com/xml/schema/linq">
  <Namespaces>
    <Namespace
      Schema="http://www.example.com/PO1" 
      Clr="LinqToXsdSamples.Schemas.PO1"/>
  </Namespaces>
</Configuration>

By choosing Schema="", one can also map an XML schema without target namespace to a CLR namespace.

De-anonymization of schemas This schema normalization can be requested as follows:

<Configuration xmlns="http://www.microsoft.com/xml/schema/linq">
  <Transformation xmlns="http://www.microsoft.com/FXT">
    <Deanonymize/>
  </Transformation>
</Configuration>

Note: Schema normalization is a very experimental feature of the current release of LINQ to XSD. The feature has not been tested to scale to arbitrary and sized schemas.

For inferred schemas, which tend to use a lot of nesting, this transformation is generally advisable. To explain the effect of the flattening transformation, let us look at a sample. Here is an element declaration that uses nested, anonymous complex types:

<xs:element name="book">
    <xs:complexType>
    <xs:sequence>
        <xs:choice maxOccurs="unbounded">
        <xs:element name="title" type="xs:string" />
        <xs:element maxOccurs="unbounded" name="author">
            <xs:complexType>
            <xs:sequence>
                <xs:element name="last" type="xs:string" />
                <xs:element name="first" type="xs:string" />
            </xs:sequence>
            </xs:complexType>
        </xs:element>
        <xs:element name="editor">
            <xs:complexType>
            <xs:sequence>
                <xs:element name="last" type="xs:string" />
                <xs:element name="first" type="xs:string" />
                <xs:element name="affiliation">
                <xs:complexType>
                    <xs:simpleContent>
                    <xs:extension base="xs:string">
                        <xs:attribute name="type" type="xs:string" use="required" />
                    </xs:extension>
                    </xs:simpleContent>
                </xs:complexType>
                </xs:element>
            </xs:sequence>
            </xs:complexType>
        </xs:element>
        <xs:element name="publisher" type="xs:string" />
        <xs:element name="price" type="xs:decimal" />
        </xs:choice>
    </xs:sequence>
    <xs:attribute name="year" type="xs:unsignedShort" use="required" />
    </xs:complexType>
</xs:element>

Mapping the above XML schema, with its nesting structure, to an object model, as is, would result in nested .NET classes, which are inconvenient to work with. Flattening results in a schema with the following complex-type definitions, which in turn are mapped to normal .NET classes, which are convenient to work with.

<xs:complexType name="book">
<xs:sequence>
    <xs:choice maxOccurs="unbounded">
    <xs:element name="title" type="xs:string" />
    <xs:element maxOccurs="unbounded" name="author" type="author" />
    <xs:element name="editor" type="editor" />
    <xs:element name="publisher" type="xs:string" />
    <xs:element name="price" type="xs:decimal" />
    </xs:choice>
</xs:sequence>
<xs:attribute name="year" type="xs:unsignedShort" use="required" />
</xs:complexType>

<xs:complexType name="author">
<xs:sequence>
    <xs:element name="last" type="xs:string" />
    <xs:element name="first" type="xs:string" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="editor">
<xs:sequence>
    <xs:element name="last" type="xs:string" />
    <xs:element name="first" type="xs:string" />
    <xs:element name="affiliation" type="affiliation" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="affiliation">
<xs:simpleContent>
    <xs:extension base="xs:string">
    <xs:attribute name="type" type="xs:string" use="required" />
    </xs:extension>
</xs:simpleContent>
</xs:complexType>

3.2 Compile-time customization

The classes generated by LINQ to XSD can be customized in the ways that are generally available for .NET object models in source form. It is normally unreasonable to actually modify the generated code itself; the code is rather complex, any code changes will be lost after class re-generation. However, subclassing or partial-class extension can be used effectively. It turns out that subclassing is of relatively limited use in practice but partial-class extension is a vital mechanism for adding members to the classes that were generated by LINQ to XSD.

We will discuss a simple example below. An advanced example is covered by the LinqToXsdDemo solution; see the sample file Evaluate.cs.

In the overview document (in Section 3.3), we experimented with the problem of formatting US addresses. At that point, we simply defined a static method, Format, in the user scope. Now it turns out that this formatting method is actually a sensible candidate for the virtual ToString method, when specializing this method for business objects for US addresses.

We recall the relevant XML schema for convenience:

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

  <xs:complexType name="USAddress">
    <xs:sequence>
      <xs:choice>
        <xs:element name="Street" type="xs:string"/>
        <xs:element name="POBox" type="xs:int"/>
      </xs:choice>
      <xs:element name="City" type="xs:string"/>
      <xs:element name="Zip" type="xs:int"/>
      <xs:element name="State" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>

Formatting can be attached as an overriding ToString implementation to the generated CLR namespace by re-entering this CLR namespace and the relevant class as follows; there is really nothing LINQ to XSD-specific about this use of partial classes, but it is worth stressing that partial classes are useful for LINQ to XSD:

namespace www.example.com.Address
{
    public partial class USAddress
    {
        public override string ToString()
        {
            string variablePart = null;
            if (this.Street != null)
                variablePart = this.Street;
            else if (this.POBox != null)
                variablePart = "PO Box " + this.POBox;
            return 
                  variablePart + "\n"
                + this.City + ", "
                + this.State + " "
                + this.Zip;
        }
    }
}

3.3 Post-compile-time customization

Partial-class extension cannot be used to adapt object models that are only available in compiled form. This fact affects a user who is not provided with the original schema or the sources for the generated classes. Extension methods (as of C# 3.0) can be used to add behavior even to compiled object types. The LINQ to XML annotation facility can be used to add data even to compiled object types.

We will discuss a simple example for extension methods below. We refer to the LINQ to XML documentation regarding the advanced subject of XElement annotations.

Suppose we have readily compiled the object model for US addresses, as used in the previous section. In this case, we cannot add new, native methods. The existing language support for extension methods can be used in a way that is similar to partial classes – even though extension methods are less expressive. That is, we may add a formatting method to the class USAddress so that formatting can be invoked through instance-method syntax.

namespace www.example.com.Address
{
    public static class USAddressExtension
    {
        public static string Format(this USAddress anAddress)
        {
            //  Functionality as before
        }
    }
}

4. Using LINQ to XSD in .NET Core apps

Install the dotnet tool nuget package by following the instructions here.

Miscellaneous

5.1 Appending setters

The overview document discussed the difference between insert vs. append semantics for setters. The following details illustrate the append semantics in greater detail. The encounter and use of append semantics is seen as an edge-case scenario for LINQ to XSD. Consider the following ‘tricky’ content model:

<xs:element name="tricky">
    <xs:complexType>
    <xs:sequence>
        <xs:element name="a" type="xs:string"/>
        <xs:sequence minOccurs="0" maxOccurs="unbounded">
        <xs:element name="b" type="xs:string"/>
        <xs:element name="c" type="xs:string" maxOccurs="unbounded"/>
        </xs:sequence>
    </xs:sequence>
    </xs:complexType>
</xs:element>

Let’s aim at the following XML tree:

<tricky>
  <a>1</a>
  <b>2</b>
  <c>3</c>
  <c>4</c>
  <b>5</b>
  <c>6</c>
</tricky>

Expression-oriented object initialization is insufficient for the construction of ‘tricky’ elements. The trouble is that b’s and c’s must be added (grouped) in a certain manner. Statement-oriented construction serves these ‘tricky’ content models.

var t = new tricky();
t.a = "1";
t.b.Add("2");
t.c.Add("3");
t.c.Add("4");
t.b.Add("5");
t.c.Add("6");

Here we essentially rely on the append semantics. That is, the desired order of the b’s and c’s is implied by using the appending setters in that order.

⚠️ **GitHub.com Fallback** ⚠️