Section 3.8. Data Sets and Tables


3.8. Data Sets and Tables

One of the most common types of data contracts exchanged between clients and services is data that originates in or is destined to a database. In .NET, the native way of interacting with databases is via ADO.NET's data set and data table types. Applications can use the raw DataSet and DataTable types, or use the data access management tools in Visual Studio to generate type-safe derivatives.

The raw DataSet and DataTable types are serializable, marked with the Serializable attribute:

 [Serializable] public class DataSet : ... {...} [Serializable] public class DataTable : ... {...} 

This means that you can define valid service contracts that accept or return data tables or data sets:

 [DataContract] struct Contact {...} [ServiceContract] interface IContactManager {    [OperationContract]    void AddContact(Contact contact);    [OperationContract]    void AddContacts(DataTable contacts);    [OperationContract]    DataTable GetContacts( ); } 

When importing the definition of this service contract above, the generated proxy file will contain the definition of the DataTable data contractonly the schema of DataTable, without any of the code. You can freely remove this definition from the file and reference ADO.NET instead.

You can also use the type-safe subclasses of DataSet and DataTable in your contract. For example, suppose you have a table in a database called ContactsDataTable, containing your contacts, with columns such as FirstName and LastName. You can use Visual Studio to generate a type-safe data set called MyDataSet, which has a nested class called ContactsDataTable, as well as a type-safe row and type-safe data adapter, as shown in Example 3-10.

Example 3-10. Type-safe data set and data table

 [Serializable] public partial class MyDataSet : DataSet {    public ContactsDataTable Contacts    {get;}    [Serializable]    public partial class ContactsDataTable : DataTable,IEnumerable    {       public void AddContactsRow(ContactsRow row);       public ContactsRow AddContactsRow(string FirstName,string LastName);       //More members    }    public partial class ContactsRow : DataRow    {       public string FirstName       {get;set;}       public string LastName       {get;set;}       //More members    }    //More members } public partial class ContactsTableAdapter : Component {    public virtual MyDataSet.ContactsDataTable GetData( );    //More members } 

You can use the type-safe data table in your service contract:

 [DataContract] struct Contact {...} [ServiceContract] interface IContactManager {    [OperationContract]    void AddContact(Contact contact);    [OperationContract]    void AddContacts(MyDataSet.ContactsDataTable contacts);    [OperationContract]    MyDataSet.ContactsDataTable GetContacts( ); } 

The data row itself is not serializable, so you cannot use it (or its type-safe subclass) in operations:

 //Invalid definition [OperationContract] void AddContact(MyDataSet.ContactsRow contact); 


The type-safe data table will be part of the published metadata of the service. When importing it to the client, SvcUtil and Visual Studio are smart enough to regenerate the type-safe data table, and the proxy file will include not just the data contract but the code itself. If the client already has a local definition of the type-safe table, you can remove the definition from the proxy file.

3.8.1. Arrays Instead of Tables

ADO.NET and the Visual Studio tools make it trivial for both a WCF client and service to use DataSet and DataTable and their type-safe derivatives. However, these data access types are specific to .NET. While they are serializable, their resulting data contract schema is so complex that trying to interact with it on other platforms is impractical. There are additional drawbacks for using a table or a data set in a service contract: you may be exposing your internal data structure to the world. Also, future changes to the database schema may affect your clients. While inside an application it may be permissible to pass the data table, sending the data table across an application or public service boundary is rarely a good idea. It is better in general to expose operations on the data as opposed to the data itself.

If you do need to pass around the data itself, it is best to do so using a neutral data structure such as an array. To streamline the task of converting a data table to an array, you can use my DataTableHelper class, defined as:

 public static class DataTableHelper {    public static T[] ToArray<R,T>(DataTable table,Converter<R,T> converter)                                                                  where R : DataRow; } 

All DataTableHelper requires is a converter from a data row in the table to the data contract. DataTableHelper also adds some compile-time and runtime type-safety verification. Example 3-11 demonstrates using DataTableHelper.

Example 3-11. Using DataTableHelper

 [DataContract] struct Contact {    [DataMember]    public string FirstName;    [DataMember]    public string LastName; } [ServiceContract] interface IContactManager {    [OperationContract]    Contact[] GetContacts( );    ... } class ContactManager : IContactManager {    public Contact[] GetContacts( )    {       ContactsTableAdapter adapter = new ContactsTableAdapter( );       MyDataSet.ContactsDataTable contactsTable = adapter.GetData( );       Converter<MyDataSet.ContactsRow,Contact> converter;       Converter = delegate(MyDataSet.ContactsRow row)                   {                      Contact contact = new Contact( );                      contact.FirstName = row.FirstName;                      contact.LastName  = row.LastName;                      return contact;                   };       return DataTableHelper.ToArray(contactsTable,converter);    }    //Rest of the implementation } 

In Example 3-11, the GetContacts( ) method uses the type-safe table adapter ContactsTableAdapter (listed in Example 3-10) to get the records from the database in the form of the type-safe table MyDataSet.ContactsDataTable. GetContacts( ), then defines an anonymous method that converts an instance of the type-safe data row MyDataSet.ContactsRow to a Contact instance. GetContacts( ) then calls DataTableHelper.ToArray( ), providing it with the table and the converter.

Example 3-12 shows the implementation of DataTableHelper.ToArray( ).

Example 3-12. The DataTableHelper class

 public static class DataTableHelper {    public static T[] ToArray<R,T>(DataTable table,Converter<R,T> converter)                                                                   where R : DataRow    {       if(table.Rows.Count == 0)       {          return new T[]{};       }       //Verify [DataContract] or [Serializable] on T       Debug.Assert(IsDataContract(typeof(T)) || typeof(T).IsSerializable);       //Verify table contains correct rows       Debug.Assert(MatchingTableRow<R>(table));       return Collection.UnsafeToArray(table.Rows,converter);    }    static bool IsDataContract(Type type)    {       object[] attributes =                      type.GetCustomAttributes(typeof(DataContractAttribute),false);       return attributes.Length == 1;    }    static bool MatchingTableRow<R>(DataTable table)    {       if(table.Rows.Count == 0)       {          return true;       }       return table.Rows[0] is R;    } } 

In essence, all DataTableHelper.ToArray( ) does is use the myCollection helper class to invoke the converter on every row in the table, converting every row to a single Contact and returning the resulted array. DataTableHelper adds some type safety. At compile time, it places constraints on the type parameter R to be a data row. At runtime, the ToArray( ) method returns an empty array if the table is empty. It verifies that the type parameter T is decorated either with the DataContract attribute or the Serializable attribute. Verifying the DataContract attribute is done via the helper method IsDataContract( ), which uses reflection to look up the attribute. Verifying the Serializable attribute is done by checking whether the IsSerializable bit is set on the type. The last verification done by ToArray( ) is to ensure that the provided table has the rows specified with the type parameter R. This is done via the MatchingTableRow( ) helper method, which gets the first row and verifies its type.




Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net