Annotating Typed DataSets

 

Annotations enable you to modify the names of the elements in your typed DataSet without modifying the underlying schema. Modifying the names of the elements in your underlying schema would cause the typed DataSet to refer to objects that do not exist in the data source, as well as lose a reference to the objects that do exist in the data source.

Using annotations, you can customize the names of objects in your typed DataSet with more meaningful names, making code more readable and your typed DataSet easier for clients to use, while leaving underlying schema intact. For example, the following schema element for the Customers table of the Northwind database would result in a DataRow object name of CustomersRow and a DataRowCollection named Customers.

<xs:element name="Customers">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="CustomerID" type="xs:string" minOccurs="0" />
    </xs:sequence>
  </xs:complexType>
</xs:element>

A DataRowCollection name of Customers is meaningful in client code, but a DataRow name of CustomersRow is misleading because it is a single object. Also, in common scenarios, the object would be referred to without the Row identifier and instead would be simply referred to as a Customer object. The solution is to annotate the schema and identify new names for the DataRow and DataRowCollection objects. Following is the annotated version of the previous schema.

<xs:element name="Customers" codegen:typedName="Customer" codegen:typedPlural="Customers">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="CustomerID" type="xs:string" minOccurs="0" />
    </xs:sequence>
  </xs:complexType>
</xs:element>

Specifying a typedName value of Customer will result in a DataRow object name of Customer. Specifying a typedPlural value of Customers preserves the DataRowCollection name of Customers.

The following table shows the annotations available for use.

Annotation

Description

typedName

Name of the object.

typedPlural

Name of a collection of objects.

typedParent

Name of the object when referred to in a parent relationship.

typedChildren

Name of the method to return objects from a child relationship.

nullValue

Value if the underlying value is DBNull. See the following table for nullValue annotations. The default is _throw.

The following table shows the values that can be specified for the nullValue annotation.

nullValue Value

Description

Replacement Value

Specify a value to be returned. The returned value must match the type of the element. For example, use nullValue="0" to return 0 for null integer fields.

_throw

Throw an exception. This is the default.

_null

Return a null reference or throw an exception if a primitive type is encountered.

_empty

For strings, return String.Empty, otherwise return an object created from an empty constructor. If a primitive type is encountered, throw an exception.

The following table shows default values for objects in a typed DataSet and the available annotations.

Object/Method/Event

Default

Annotation

DataTable

TableNameDataTable

typedPlural

DataTable Methods

NewTableNameRow

AddTableNameRow

DeleteTableNameRow

typedName

DataRowCollection

TableName

typedPlural

DataRow

TableNameRow

typedName

DataColumn

DataTable.ColumnNameColumn

DataRow.ColumnName

typedName

Property

PropertyName

typedName

Child Accessor

GetChildTableNameRows

typedChildren

Parent Accessor

TableNameRow

typedParent

DataSet Events

TableNameRowChangeEvent

TableNameRowChangeEventHandler

typedName

To use typed DataSet annotations, you must include the following xmlns reference in your XML Schema definition language (XSD) schema. (To create an xsd from database tables, see WriteXmlSchema or Working with Datasets in Visual Studio).

xmlns:codegen="urn:schemas-microsoft-com:xml-msprop"

The following is a sample annotated schema that exposes the Customers table of the Northwind database with a relation to the Orders table included.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="CustomerDataSet" 
      xmlns:codegen="urn:schemas-microsoft-com:xml-msprop"
      xmlns="" 
      xmlns:xs="http://www.w3.org/2001/XMLSchema" 
      xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="CustomerDataSet" msdata:IsDataSet="true">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element name="Customers" codegen:typedName="Customer"
codegen:typedPlural="Customers">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CustomerID"
codegen:typedName="CustomerID" type="xs:string" minOccurs="0" />
              <xs:element name="CompanyName"
codegen:typedName="CompanyName" type="xs:string" minOccurs="0" />
              <xs:element name="Phone" codegen:typedName="Phone"
codegen:nullValue="" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Orders" codegen:typedName="Order"
codegen:typedPlural="Orders">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="OrderID" codegen:typedName="OrderID"
type="xs:int" minOccurs="0" />
              <xs:element name="CustomerID"
codegen:typedName="CustomerID"
                 codegen:nullValue="" type="xs:string" minOccurs="0" />
              <xs:element name="EmployeeID"
codegen:typedName="EmployeeID" codegen:nullValue="0" 
type="xs:int" minOccurs="0" />
              <xs:element name="OrderAdapter"
codegen:typedName="OrderAdapter"
codegen:nullValue="1980-01-01T00:00:00" 
type="xs:dateTime" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
    <xs:unique name="Constraint1">
      <xs:selector xpath=".//Customers" />
      <xs:field xpath="CustomerID" />
    </xs:unique>
    <xs:keyref name="CustOrders" refer="Constraint1"
codegen:typedParent="Customer" codegen:typedChildren="GetOrders">
      <xs:selector xpath=".//Orders" />
      <xs:field xpath="CustomerID" />
    </xs:keyref>
  </xs:element>
</xs:schema>

The following code example uses a strongly typed DataSet created from the sample schema. It uses one SqlDataAdapter to populate the Customers table and another SqlDataAdapter to populate the Orders table. The strongly typed DataSet defines the DataRelations.

// Assumes a valid SqlConnection object named connection.
SqlDataAdapter customerAdapter = new SqlDataAdapter(
    "SELECT CustomerID, CompanyName, Phone FROM Customers",
    connection);
SqlDataAdapter orderAdapter = new SqlDataAdapter(
    "SELECT OrderID, CustomerID, EmployeeID, OrderAdapter FROM Orders", 
    connection);

// Populate a strongly typed DataSet.
connection.Open();
CustomerDataSet customers = new CustomerDataSet();
customerAdapter.Fill(customers, "Customers");
orderAdapter.Fill(customers, "Orders");
connection.Close();

// Add a strongly typed event.
customers.Customers.CustomerChanged += new 
  CustomerDataSet.CustomerChangeEventHandler(OnCustomerChanged);

// Add a strongly typed DataRow.
CustomerDataSet.Customer newCustomer = 
    customers.Customers.NewCustomeromer();
newCustomer.CustomerID = "NEW01";
newCustomer.CompanyName = "My New Company";
customers.Customers.AddCustomer(newCustomer);

// Navigate the child relation.
foreach(CustomerDataSet.Customer customer in customers.Customers)
{
  Console.WriteLine(customer.CustomerID);
  foreach(CustomerDataSet.Order order in customer.GetOrders())
    Console.WriteLine("\t" + order.OrderID);
}

protected static void OnCustomerChanged(object sender, CustomerDataSet.CustomerChangeEvent e)
    {

    }
Show: