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_ ;-).

Service Container

Now the Service Container (Inversion of Control and Dependency Injection) concepts are being adopted by the Microsoft Pattern & Practices group in their CAB and EntLib frameworks, maybe I can talk about a service container implementation I made a few months ago (BTW: A service is a course-grained piece of reusable functionality and not -necessarily- a web service!).

I noticed that everyone who has made a service container (or service locator) framework implemented a custom container interface. But the System.ComponentModel contains a decent IServiceProvider interface that is a suitable client interface. It defines one method GetService that takes a Type parameter to specify the service (interface) type. Visual Studio uses a service container when (form/web) controls are hosted on a designer surface.

So I set out to design the basic structure that is needed for building a service container. After examinig the System.ComponentModel namespace further an IComponent, IContainer and an ISite interface came to light. It appears that these interfaces are used in the standard service container mechanism that is already present in the .NET framework.

The (I)Container manages a collection of (I)Component instances (the Container is NOT a ServiceContainer). When a Component is added to a (and only one) Container it is ‘sited’. A (I)Site is what ties a Component to its Container. Notice that the ISite interface is derived from IServiceProvider interface. So, whenever the Component needs a service it only needs to ask its Site using the GetService method for the service it requires.

The default .NET implementation creates a no-functionality Site instance for each Component that is added to a Container. Luckily for us there’s a virtual method on the Container class (CreateSite) we can override to create a Site of our own making. We need to because we still have no ServiceContainer so far and the default implementation provider by the System.ComponentModel doesn’t provide in one, either.

The way I see it is that the Site provides a way to give each Component a unique context and because the Site already implements the IServiceProvider interface it is the logical place to place the ServiceContainer. My design explicitly defines a ServiceContainer class but logically the Site and ServiceContainer could be thought of as one and the same. This means that it is possible to regulate the service implementations each component has access to through its Site.

This means, for example, that if you have a WebPart page and you bootstrap each WebPart (=Component) to a Container, you can control what services are provided to each WebPart. Or if you have a Domain Object Model (=Component) and bootstrap each instance to a Container, you could control the way these instances perform their tracing, because you control the actual Trace Service implementation used for these objects. Must be said that I assume that only a Service interface is published to the clients (Components) not its implementation class.

But how does the framwork know what services to load for a specific component? The framework looks in the .config file for that. The .config file contains a section that describes services. What classes to use and maybe some service specific configuration settings. It also contains a service profile section. A service profile is a list of services that is loaded into a ServiceContainer. At this point there’s also room to specify custom service config settings. Finaly there’s a context binding section. This section maps (for instance) a Component’s name to a service profile that describes the services available for that Component. Future implementations of the framework will probably also include a service policy section to describe settings like lifetime management and other runtime aspects. Lifetime management of the Service instances is not implemented yet. At the moment the Service instance is created (on demand) and cached in the ServiceContainer. Singleton or PerCall (or custom) instance management is something you’ll want to have eventually.

What will happen if a Component requests a service that is not available in its Site/ServiceContainer. The framework  allows for a Component hierarchy, where a Component may host a Container that contains their child Component instances. So, if a service request can not be satisfied, it is passed to the parent ServiceContainer and so on, untill the root ServiceContainer is reached. This also implies that service clients (Components) can handle the absence of a service they request (handling this scenario can involve throwing an exception ofcourse ;-).

The root ServiceContainer also contains the Services used by the framework itself. The way the framework obtains its configuration settings is implemented as a service, which gives you the option to replace the configuration source.

Take a look at the gotdotnet workspace (if you’re still interested 😉 where you can download a short ppt and access the source code.Future plans for this framework include incorporating the ObjectBuilder (providing a free DI framework) and providing an alternate Configuration Service for the EntLib configuration app block.

Any thoughts, suggestions or comments are most welcome.