« Election results thoughts | Main | C# covariance and contravariance by example »

December 21, 2008

ASP.NET Dynamic Data and custom sources

ASP.NET Dynamic Data comes with two data sources: LINQ to SQL and the Entity Framework.  But what if you want to create a Dynamic Data site that uses a different data access technology?  It turns out that there are two things you need to implement, and one change you need to make to the default template.

One prerequisite is that the data access technology must support LINQ, or at least a subset thereof.  Specifically, Dynamic Data creates LINQ queries to select individual records or to filter tables by foreign key or value.  So your platform's LINQ implementation will need basic "where" support but should not need joins or grouping.

With that prerequisite in place, what do you need to do over and above having a LINQ-capable data source?

1. Implement a model provider stack.

Dynamic Data depends on a set of providers to tell it about the model, the tables, the columns and the associations between tables.  The provider stack is also partially responsible for creating query objects.  So you'll need to implement the following four classes:

DataModelProvider.  You need to do two things here: create a list of TableProviders, and create "context objects" on demand.  The context object is the "unit of work" for the Dynamic Data site, analogous to a LINQ to SQL DataContext.

TableProvider.  Again, two main things to do here: create a list of ColumnProviders, and create a query object.  (You'll also need to set up some basic properties like name and data type.)  The query object must implement IQueryable, because this is what Dynamic Data is going to feed its LINQ queries to.

A typical pattern here is that the context object type has a bunch of IQueryable properties, and the DataModelProvider returns a TableProvider for each such property, and that TableProvider's query object is the value of that property on the context object instance.  (This is how LINQ to SQL works, with each Table being an IQueryable property of the DataContext.  Strongly typed units of work in LINQ to LightSpeed also fit this pattern.)  For example the context type contains a Products property of type IQueryable<Product>, giving rise to a Product TableProvider, whose query object for a given context is context.Products.  This is probably easier to show than to describe:

public class MyDataModelProvider : DataModelProvider
{
  private void InitialiseColumns()
  {
    foreach (PropertyInfo property in _contextType.GetProperties().Where(p => typeof(IQueryable).IsAssignableFrom(p.PropertyType))
    {
      _tableProviders.Add(new MyTableProvider(property));
    }
  }
}

public class MyTableProvider : TableProvider
{
  public IQueryable GetQuery(object context)
  {
    return (IQueryable)(_property.GetValue(context, null));
  }
}

ColumnProvider.  Mostly you just need to set up the basic properties like name and data type.  The only complexity comes when you have a column or property that represents an association.  In this case you also need to create an AssociationProvider.

AssociationProvider.  These can be a bit confusing and tricky to implement: firstly, it's not always obvious from the documentation what properties you need to set and to what values, and secondly, you need some way of identifying reverse associations.

Here are the values you need to set for the AssociationProvider base class properties:

FromColumn: the ColumnProvider for which the AssociationProvider is being created.

Direction: as appropriate.

ToTable: the TableProvider corresponding to the target type of the association (the singular type in the case of a one-to-many association).

ToColumn: the ColumnProvider in the ToTable corresponding to the reverse association.

ForeignKeyNames: in a many-to-one association, the name of the foreign key column underlying the relationship.  You don't need to set this property for a one-to-many association.

Let's look at an example.  Consider an association between Albums and Tracks.  Album has a Tracks property which is a collection of Tracks, and Track has an Album property of type Album.  Then the ColumnProvider for Album.Tracks should have an Association whose provider looks like this:

  • Direction: OneToMany
  • FromColumn: the ColumnProvider for Album.Tracks
  • ToTable: the TableProvider for Track
  • ToColumn: the ColumnProvider for Track.Album

And the ColumnProvider for Track.Album should have an Association whose provider looks like this:

  • Direction: ManyToOne
  • FromColumn: the ColumnProvider for Track.Album
  • ToTable: the TableProvider for Album
  • ToColumn: the ColumnProvider for Album.Tracks
  • ForeignKeyNames: { "AlbumId" }

Determining the reverse associations programmatically can be tricky depending on how much metadata the platform exposes.  For example, LightSpeed's metamodel is internal, and the LightSpeed ReverseAssociationAttribute goes on the backing field of an association, not on the property that the ColumnProvider would typically have access to.  (The solution in LightSpeed's case is to locate the backing field by taking advantage of LightSpeed's predictable naming conventions.  But other platforms will demand other solutions.)

2. Implement a custom data source control.

Actually, all you really want to do is implement a custom data source view, but the only way to get Dynamic Data to use a custom data source view is to use a custom data source control.

Your custom data source control can derive from LinqDataSource and just override CreateView to return an instance of your custom data source view.

Your custom data source view requires a little bit more effort.  Again, start by deriving from LinqDataSourceView.  This offers some tempting virtual methods below the level of ExecuteInsert, ExecuteUpdate and ExecuteDelete, such as UpdateDataObject, so you would hope you could just override those and piggyback off the base class Execute methods for tedious things like event flow.  Unfortunately unless your data access technology is very like LINQ to SQL, there's a good chance this won't work, and you'll have to re-implement the Execute methods from the ground up.  The good news is that these are still pretty routine: the main trick is that you'll need the context object from the model provider (because this is your unit of work), and you get this by calling OnContextCreating().

3. Change the site to use the custom data source.

The Visual Studio template for Dynamic Data Web sites specifies an <asp:LinqDataSource> on each of its template pages.  You'll need to changes each of these to refer to your custom data source instead.  You don't need to change the content or attributes, just the control type.

You shouldn't need to make any other changes to the pages, but when specifying the data source in Global.asax.cs, you will of course need to pass an instance of your DataModelProvider instead of a LINQ to SQL DataContext.

December 21, 2008 in Software | Permalink

TrackBack

TrackBack URL for this entry:
https://www.typepad.com/services/trackback/6a00d8341c5c9b53ef0105368fc133970c

Listed below are links to weblogs that reference ASP.NET Dynamic Data and custom sources:

Comments

Hi,

Do you have a sample project where you were able to make this work. I have been trying creating a custom data model provider but I just can't seem to make it work.

Thanks,
Ryan

Posted by: ryan at Jan 21, 2009 9:12:38 PM