-
Notifications
You must be signed in to change notification settings - Fork 15
Mapping documentation
The following document systematically describes the mapping of XML schemas to .NET object models. The normal user of the LINQ to XSD technology is not necessarily assumed to work with this documentation. The idea is that the implemented mapping is mostly intuitive and that intellisense and object browsing provide sufficient help for the developer. The documentation mainly serves as an informal specification.
The resulting .NET classes are described in C#-like pseudo code so as to hide routine technicalities.
All shown classes and members are implicitly public.
- Hence, the keyword public is universally omitted.
- Constructors are marked by the keyword constructor -- for clarity.
- We are not using C#'s verbose notation "static explicit operator".
- Instead we use the short notation explicit cast.
- We elide the implementations of all property members.
- To this end, we use "interface notation":
{ get; set; }
.
class Foo : XTypedElement
{
constructor Foo();
explicit cast Foo(System.Xml.Linq.XElement xe);
property string Bar { get; set; }
}
- Complex-type definitions
- Roots with anonymous, complex types
- Roots with type references to complex types
- Simple-type definitions
- Roots with anonymous, simple types
- Roots with type references to simple types
- XSD's forms of element and type substitution are discussed separately.
- Some other abstraction forms such as attribute declarations are mapped trivially.
Complex-type definitions are mapped to classes.Each generated class comprises the following members:
- a default constructor;
- an explicit cast from XElement to the defined class;
- some inherited API members such as Save; see the LINQ to XSD manual;
- further members depending on the content model.
The class that is generated for a complex type subclasses
XTypedElement
. In fact, a derived complex-type is mapped to a subclass of the class that is generated for its base type.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- A complex type for addresses -->
<xs:complexType name="Address">
<!-- address details omitted -->
</xs:complexType>
</xs:schema>
class Address : XTypedElement
{
constructor Address();
explicit cast Address(System.Xml.Linq.XElement xe);
}
We use the term "root" to refer to global element declarations in an XML schema.
We recall that a valid instance must be necessarily rooted in an element of that kind.
Such roots are essentially mapped in the same way as complex-type definitions.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- A global element declaration for addresses -->
<xs:element name="Address">
<xs:complexType>
<!-- address details omitted -->
</xs:complexType>
</xs:element>
</xs:schema>
class Address : XTypedElement
{
constructor Address();
explicit cast Address(System.Xml.Linq.XElement xe);
}
The conceptual difference between complex-type definitions and root element declarations is worth recalling: The latter define proper types in the sense of sets of elements, whereas the former model "incomplete" elements because element tags are not prescribed. This conceptual difference is reflected in the behavior of the generated object types. That is, the complex-type name serves as "preliminary" element tag for any instance of the class that corresponds to the complex type in question; this preliminary tag is eventually resolved to a proper element tag, when the instance is parented or when it is wrapped in a root element.
Note: Save and Load members are potentially misleading for classes that were generated from complex-type definitions since de-/serialization is only well-defined for root-element declarations. It is conceivable that a future release of LINQ to XSD makes explicit the conceptual difference between complex-type definitions and root element declarations.
This pattern makes combined use of a complex-type definition and a global element declaration.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="BillTo" type="Address"/>
<xs:element name="ShipTo" type="Address"/>
<xs:complexType name="Address">
<xs:sequence>
<xs:element name="Name" type="xs:string"/>
<!-- ... -->
</xs:sequence>
</xs:complexType>
</xs:schema>
This style is mapped as follows:
- The following classes are generated in this case:
- There is one class for each element declaration ("the envelope").
- There is one class for each complex type ("the content").
- Envelopes can be constructed from content by means of a non-default constructor.
- The content can be extracted from the envelope by means of the Content member.
- The "envelope" class implements the API of the "content" class by forwarding.
class Address : XTypedElement
{
constructor Address();
explicit cast Address(System.Xml.Linq.XElement xe);
property string Name { get; set; }
}
class BillTo : XTypedElement
{
constructor BillTo();
constructor BillTo(Address content);
explicit cast BillTo(System.Xml.Linq.XElement xe);
property Address Content { get; }
property string Name { get; set; }
}
class ShipTo : XTypedElement
{
constructor ShipTo();
constructor ShipTo(Address content);
explicit cast ShipTo(System.Xml.Linq.XElement xe);
property Address Content { get; }
property string Name { get; set; }
}
The combined use of element declarations and complex-type definitions leads to potentially convoluted object models, when the most direct mapping is applied. Hence, this usage pattern of XSD may suggest schema normalizations. Note: The current release of LINQ to XSD does not yet provide any relevant schema normalization.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="Month">
<xs:restriction base="xs:token">
<!-- restriction facets omitted -->
</xs:restriction>
</xs:simpleType>
</xs:schema>
By default, simple-type definitions are not mapped to any class that the programmer is supposed to instantiate.
Note: The current release of LINQ to XSD generates a vacuous class for each simple-type definition. This class should be omitted. By customization, one can also request a class that is designated to a simple-type definition.
Note: This option not supported by the current release of LINQ to XSD. The use of designated classes offers two benefits: (i) simple-type names from the XML schema can be used in program code; (ii) substitutability for simple types (say, subtyping in OO terms) can be precisely expressed.
Root element declarations (with anonymous types or not) are mapped to classes.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Month">
<xs:simpleType>
<xs:restriction base="xs:string">
<!-- restriction facets omitted -->
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:schema>
class Month : XTypedElement
{
constructor Month();
explicit cast Month(System.Xml.Linq.XElement xe);
property string TypedValue { get; set; }
}
The (simple) content of such root elements can be access with a designated TypedValue property (akin to LINQ to XML's untyped value property). The type of this property is the built-in base type of the anonymous simple type. As usual, the class also provides an explicit coercion (cast) from XElement
to the class.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Month" type="MonthType"/>
<xs:simpleType name="MonthType">
<xs:restriction base="xs:string">
<!-- restriction facets omitted -->
</xs:restriction>
</xs:simpleType>
</xs:schema>
class Month : XTypedElement
{
constructor Month();
constructor Month(string content);
explicit cast Month(System.Xml.Linq.XElement xe);
property string TypedValue { get; set; }
}
The (simple) content of such root elements can be access with a designated TypedValue property (akin to LINQ to XML's untyped value property). The type of this property is the OO counterpart for the built-in XSD base type of the referenced simple type. As usual, the class also provides an explicit coercion (cast) from XElement
to the class.