-
Notifications
You must be signed in to change notification settings - Fork 15
Manual
This manual covers some topics of LINQ to XSD that a user may need to know.
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).
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.
The types in running example of the LINQ to XSD overview use the following constructors:
public Batch();
public PurchaseOrder();
public Item();
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 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.
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.
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.
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.
Note: This topic is undocumented for the current release of LINQ to XSD.
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();
}
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.
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));
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");
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();