DataSet Manager

Why are OR-mappers cool? I dont know? My experience with them has been limited and the time I did use them the support for common constructs was very poor (at best): I don’t think its a good idea for a OR-mapper to caugh up a SQL select query for each instance that needs to go in a collection. The framework should be able to retrieve any hierarchy of data in one round trip (not counting the sceanrio where you have to decide how big the data packet may get versus how many roundtrips are optimal). Also I believe the OR-Mapper problem domain is two fold: 1) you need a good data access layer that supports all the features the required for a "good" OR-mapper and 2) you need the OR-mapping itself, map relational data to objects and back, which is a relatively simple problem to solve.

So I started thinking about the data access layer. I’m a big fan of typed DataSets. If you know your VS.NET you can whip up a typed DataSet within 5 minutes. My assumtion is that the data access layer works with (typed) DataSets. Also I propose to put your application data model in one type DataSet. For instance, You put the complete datamodel of Northwind into one typed "Nortwind" DataSet. If you have a really big solution you could work with subsets for each sub-system.

Now I want to be able to express my data queries in the same ‘entities’ defined in my application typed DataSet (Northwind). Why would an application programmer have to express his data requests in a different "language" than his application data model? Now we need an entry point that will handle our data requests and knows about our application data model. Enter the DataSet Manager.

The way the DataSet Manager retrieves data is by using the structure defined in the (typed) DataSet. It needs a copy of this structure as its data model. For now we assume that this application model reflects the physical data model in the database. A "Real" mapper would allow a certain abstraction here, allowing your application business entities to be subsets (or combined) database entities. The DataSet manager would have (overloaded) methods to fecth data for a (running) typed application DataSet. For instance "Fill up the (Northwind.)Empoyees table", "Fetch all orders handled by this Employee", make changes to the dataset and save.

In the following code examples we assume we have a typed DataSet named Northwind with the complete Northwind database schema.

// create the DataSetManager and initialize the DataModel
DataSetManager mgr = new DataSetManager();
mgr.CreateDataModel(new Northwind(), "Initial Catalog=Northwind");

This code creates a new DataSetManager and initializes the instance with the application data model to use and a connection string to the physical database. Inside the DataSetManager the schema of the (typed) DataSet is analysed and the DataSetManager creates DataAdapters (in this prototype) for each DataTable. The DataSetManager is ready.

// load all Employees data into an empty dataset
Northwind dataSet = new Northwind();
mgr.Fill(dataSet, dataSet.Employees);

Notice the second Northwind DataSet instance. The first was passed to the DataSetManager as a schema, this one is used (by the application programmer) for actually storing data. We ask the DataSetManager to fill up the Employees table and pass it the running DataSet and a reference to the table definition. Because we use typed DataSets both are contained in one instance (thats what makes a DataSet typed). All methods of the DataSetManager take a DataSet as a first parameter. This allows for seperation of data holder and data definition. The DataSetManager will build a "Select * from Employees" query for this method call and writes the results back to the (running) DataSet.

But wait a minute. If EnforceConstraints is set to true this won’t work. The lack of data in the other tables the Employee table has a DataRelation with will cause the contraints to fail. Not quite so. The DataSetManager knows about the schema and therefor knows about these relations too. It examines the content of the passed dataset and dissables these contraints that ‘point’ to empty tables. If you pass in a dataset with its EnforceConstraints set to false, the DataSetManager does nothing.

// find all Orders for the first Employee
Northwind.EmployeesRow employee = dataSet.Employees[0];
mgr.Expand(dataSet, employee, DataSetManager.FindRelation(dataSet.Employees, dataSet.Orders));

We retrieve a reference to an Employee and ask the DataSetManager to expand for this Employee instance (DataRow) using the relation between Employees and Orders. We use a helper method to find the DataRelation between these tables. Again the order data concerning the employee is placed in the passed dataset.

// change some data
employee.Notes = String.Format("Handled {0} orders.", dataSet.Orders.Count);
// update all changes made to the dataset
mgr.Update(dataSet);

Now we change some data in the dataset (on the employee) and ask the DataSetManager to persist the changes back to the database. Using the standard DataSet.GetChanges method and using a DataAdapter the empoyee is updated in the database.

These are the method (overloads) the DataSetManager supports:

public DataSet DataModel{get;}
public void CreateDataModel(DataSet dataModel, string
connectionString)
public int
Fill(DataSet dataSet)
public int
Fill(DataSet dataSet, DataTable table)
public int
Fill(DataSet dataSet, DataRelation relation)
public int
Expand(DataSet dataSet, DataTable table)
public int
Expand(DataSet dataSet, DataRow row)
public int
Expand(DataSet dataSet, DataRelation relation)
public int
Expand(DataSet dataSet, DataRow row, DataRelation relation)
public int
Update(DataSet dataSet)
public int Update(DataTable table)

This prototype was written using .NET 1.1 and therefor no generics are used. But in a future version this would certainly be an option for added type safety. One thing thats missing from this prototype is where-clauses. This is one of the problems i’m still wresteling with. How would you express filter criteria using application entities? I’ve considered Query by example but abandoned that path. The problem with QbE is that you would introduce yet another instance of the application typed DataSet used for holding the filter criteria. And the other problem is that complex filters are difficult to express using QbE. The only other option would be to define yet another proprietary object model for expressing filter criteria.

Also notice that this is no OR-mapper (yet). Its just a really intuitive way to work with your application data. The OR-bit would map back and forth between your application typed DataSet and your (domain) objects. The real power of OR-mappers is not in the mapping part but in the data access layer.

So I would really like to hear your comments, suggestions and objections and if you want to see the DataSetManager internals drop me a line at obiwanjacobi@nospam_hotmail.com (remove the nospam_ ;-).

  1. One thought on “DataSet Manager

    Hi,
    I contacted Duong by mail and suggested to use a dummy record for cyclic dependencies. The topological sort the DataSetManager prototype uses to determine the order of updates/inserts does not work for cyclic dependencies.

    The dummy record is deleted when all records are changed in the cyclic dependency and the foreign key to the dummy re-routed to the intended key value. Be sure to use a transaction to be able to roll back in case of errors.

Comments are closed.