Tuesday, November 30, 2010

Generating a Nullable ADO.NET DataSet Wrapper

Part 2: Describing the DataSet

The next thing I need for code generation is a reliable source of meta data describing the code I want to generate. Here I merely ask the DataSet to describe itself and store the description into a set of simplified description objects. I am moving the metadata from the DataSet into a simpler object to make it easier to deal with when I start generating code in later parts of this series.

public static DataSetDef GetDataSetDefFromDS(DataSet ds) 
{ 
    // Code for this class is in “The Describer Classes” section below 
    var dsDef = new DataSetDef(ds.DataSetName, ds.Namespace); 
    // The DataSet has a collection of DataTables 
    foreach (var rawTable in ds.Tables) 
    { 
        DataTable tab = (DataTable)rawTable; 
        // See “The Describer Classes” 
        var tabDef = new TableDef(tab.TableName); 
        // And each DataTable has a collection of DataColumns 
        foreach (var rawColumn in tab.Columns) 
        { 
            DataColumn col = (DataColumn)rawColumn; 
            // See “The Describer Classes” 
            var colDef = new ColumnDef(col.ColumnName, col.DataType, col.AllowDBNull); 
            tabDef.ColumnDefList.Add(colDef); 
        } 
        dsDef.TableDefList.Add(tabDef); 
    } 
    return dsDef; 
} 

Getting the DataSet to Interrogate

OK, I can generate a description of the data from a DataSet, but the DataSet is in a different project. I want to make a tool that I can use

XSD Schema File

When you create a typed DataSet in Visual Studio, creates a .xsd file that it uses to generate your DataSet code. I can read the schema from an XSD file into a DataSet using the ReadXmlSchema() method:

public static DataSetDef GetDataSetDefFromXsd(string XsdPath) 
{ 
    DataSet ds = new DataSet(); 
    try 
    { 
        ds.ReadXmlSchema(XsdPath); 
        // Call the method above: 
        return GetDataSetDefFromDS(ds); 
    } 
    catch // I don’t care what it is right now 
    { 
        return null; 
    } 
} 

In an Assembly

The DataSet may exist in a previously existing assembly; we may not even have access to the source code. Using reflection, we can activate the type and cast it as a DataSet:

public static DataSetDef doRefelctDataSetFromType(Type myObjectType) 
{ 
    // Create an instance of the Type: 
    object obj = Activator.CreateInstance(myObjectType); 
    // Try to cast it as a DataSet: 
    DataSet ds = obj as DataSet; 
    if (ds != null) 
    { 
        // Call the method above: 
        return GetDataSetDefFromDS(ds); 
    } 
    return null; 
} 

The Describer Classes

These are the classes to make the objects loaded in GetDataSetDefFromDS(). They represent a simplified description of the data. I only get the data that I think I need to generate the code later. If I later need more data, I can alter GetDataSetDefFromDS() and these classes add what I need.

public class DataSetDef 
{ 
    public List TableDefList { get; private set; } 
    public DataSetDef() 
    { 
        TableDefList = new List(); 
    } 
    public DataSetDef(string dataSetName, string nameSpace) : 
        this() 
    { 
        DataSetName = dataSetName; 
        NameSpace = nameSpace; 
    } 
    public string DataSetName { get; set; } 
    public string NameSpace { get; set; } 
} 
public class TableDef 
{ 
    public List ColumnDefList { get; private set; } 
    public TableDef() 
    { 
        ColumnDefList = new List(); 
    } 
    public TableDef(string tableName) : 
        this() 
    { 
        TableName = tableName; 
    } 
    public string TableName { get; set; } 
} 
public class ColumnDef 
{ 
    public ColumnDef(string columnName, Type dataType, bool allowDBNull) 
    { 
        ColumnName = columnName; 
        DataType = dataType; 
        AllowDBNull = allowDBNull; 
    } 
    public string ColumnName { get; set; } 
    public Type DataType { get; set; }  // .NET Type, not SQL Type 
    public bool AllowDBNull { get; set; } 
} 

Generating a Nullable ADO.NET DataSet Wrapper: The Series

Wednesday, November 24, 2010

Generating a Nullable ADO.NET DataSet Wrapper

Part 1: The Target Code

Classic ADO.NET is the most successful client side database technology in the history of Microsoft. Even if the Entity Framework (officially "ADO.NET Entity Framework") is as successful as Microsoft thinks it will be, ADO.NET DataSets will be around for years.

One of the biggest weaknesses of the ADO.NET DataSet is the turn of the century way it handles nullability. Since Nullable didn’t exist until .NET 2, the designers of the DataSet handled nullability with two methods and one exception. I decided to generate a nullable wrapper for ADO.NET DataSets.

Target Code

To generate code, I need sample target code, so I know where I am going

DataRow

I plan on wrapping each typed DataRow in my own proxy class:

public class EmployeesRowX
{
    // The wrapped row
    NorthwindDS.EmployeesRow _row;
    public EmployeesRowX(NorthwindDS.EmployeesRow row)
    {
        _row = row;
    }
    // Direct access to the row, if you need something I didn’t implement
    public NorthwindDS.EmployeesRow Row
    {
        get { return _row; }
    }
    // …
}

For a non-nullable field, I just pass the data back without doing anything fancy:

public string FirstName
{
    get { return _row.FirstName; }
    set { Row.FirstName = value; }
}

For a nullable field, I have to translate ADO.NET the pre-nullable logic (IsXXXNull() & SetXXXNull()) to a modern nullable:

public DateTime? BirthDate
{
    get
    {
        if (_row.IsBirthDateNull())
            return null;
        else
            return _row.BirthDate;
    }
    set
    {
        if (!value.HasValue)
            _row.SetBirthDateNull();
        else
           _row.BirthDate = value.Value;
    }
}

DataSet and DataTables

I will wrap the DataSet and DataTables in their own proxies:

public class NorthwindDSX
{
    private NorthwindDS _ds;
    public NorthwindDSX(NorthwindDS ds)
    {
        _ds = ds;
    }

    public EmployeesDataTableX Employees
    {
        get { return new EmployeesDataTableX(_ds.Employees); }
    }

    public NorthwindDS DataSet
    {
        get { return _ds; }
    }
}

public class EmployeesDataTableX: IEnumerable
{
    private NorthwindDS.EmployeesDataTable _theTable;
    public EmployeesDataTableX(NorthwindDS.EmployeesDataTable table)
    {
        _theTable = table;
    }

    public NorthwindDS.EmployeesDataTable Table
    {
        get { return _theTable; }
    }

    #region IEnumerable Members
    public IEnumerator GetEnumerator()
    {
        return new EmployeesTableEnum(_theTable);
    }
    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        return (IEnumerator)this;
    }
    #endregion
}

DataTable’s IEnumberator<T>

And each Table needs an Enumerator:

public class EmployeesTableEnum : IEnumerator
{
    private NorthwindDS.EmployeesDataTable _collection;
    private int curIndex;
    private EmployeesRowX curBox;

    public EmployeesTableEnum(NorthwindDS.EmployeesDataTable collection)
    {
        _collection = collection;
        curIndex = -1;
        curBox = default(EmployeesRowX);
    }

    public bool MoveNext()
    {
        //Avoids going beyond the end of the collection.
        if (++curIndex >= _collection.Count)
            return false;
        else
            // Set current EmployeesRowX to next item in collection.
            curBox = new EmployeesRowX(
                (NorthwindDS.EmployeesRow)_collection.Rows[curIndex]);
        return true;
    }

    public void Reset() { curIndex = -1; }

    void IDisposable.Dispose() { }

    public EmployeesRowX Current
    {
        get { return curBox; }
    }

    object IEnumerator.Current
    {
        get { return Current; }
    }
}

Summing it up

So now that I know where I am going, I can get started at making generating code. The code will change as I work through the process.

Since a Typed DataSet is generated, it is possible to generate a full DataSet with nullable fields; that is a job for Microsoft.

Generating a Nullable ADO.NET DataSet Wrapper: The Series