In this post I am going to delve into the implementation of the Custom Data Service Provider that will unify the Type metadata, Dynamic Type generation and WCF OData services.
For brevity of this post I am not going to cover all of the classes required for the implementation of the Custom Data Service Provider just those that are key to this implementation. Implementing a Custom Data Service Provider is actually a relatively straight forward task once you get your head around the interfaces involved. The details behind the steps required in
Creating a Data Service Provider has been covered in a series of excellent posts by
Alex D James on his blog. I have based my implementation on some of the concepts expressed in those posts.
Class Model Overview

There are a number of classes required to expose the underlying custom data source as a service. I will discuss each one in turn to give a simple overview.
DynamicDataService is an abstract base class that derives from
DataService<
DynamicDataContext> and the
IServiceProvider interface. The purpose of this class is to and allow the WCF OData service infrastructure to instantiate the various custom data service provider types via the
object GetService(Type serviceType) method of the
IServiceProvider interface.
DynamicDataContext is the class that represents the underlying data source that will be exposed via an OData service. It derives from
DataContext and the
IDynamicDataContext interface.
The
IDynamicDataContext interface is a custom interface to abstract the implementation so that the classes of the custom data provider can be unit tested. It derives from
DataContext because it is a LINQ to SQL based data source, I will discuss this class in more detail later in this post.
DomainModelService inherits from the abstract base class
DynamicDataService. It implements the two abstract methods from the base class:
public abstract IDynamicDataServiceMetadataProvider GetMetadataProvider();
public abstract IDataServiceQueryProvider GetQueryProvider(IDataServiceMetadataProvider metadata);
The
DomainModelService generates types using the
Data Type Generator to generate the Entity types based on metadata that is provided to the service when it is instantiated. The metadata is also used to create the
DynamicDataServiceMetadataProvider instance, the
DomainModelService class will be discussed in more detail later in this post.
DynamicDataServiceMetadataProvider derives from the
IDynamicDataServiceMetadataProvider interface, the
IDynamicDataServiceMetadataProvider interface is derived from the
IDataServiceMetadataProvider interface, its purpose is to simply facilitate unit testing. The
DynamicDataServiceMetadataProvider class is simply a container for the metadata for the OData service.
DynamicDataServiceQueryProvider derives from the
IDataServiceQueryProvider interface and provides the implementation of the
IQueryable GetQueryRootForResourceSet(ResourceSet resourceSet)
DynamicDataServiceUpdateProvider derives from
IDataServiceUpdateProvider interface and provides the mechanisms by which the OData service framework can create update and delete the exposed data type entities.
In summary the key classes of the implementation are the
DomainModelService and the
DynamicDataContext. The other classes in reality are pretty much boiler plate and depend mainly on the implementation of the
DynamicDataContext.
DomainModelService
The
DomainModelService is the primary touch point for exposing the dynamically defined entities types via the WCF OData service framework.
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class DomainModelService : DynamicDataService
{
private const String AssemblyName = "DomainModel";
private Dictionary<EntityDescriptor, Type> _entityToTypeMap;
private Dictionary<ResourceSet, Type> _resourceSetToTypeMap;
private IDynamicTypeGenerator _dynamicTypeGenerator;
/// <summary>
/// Initializes a new instance of the <see cref="DomainModelService"/> class.
/// </summary>
/// <param name="domainDescriptor">The <see="DomainModelDescriptor"> that this service has to represent.</param>
public DomainModelService(DomainModelDescriptor domainDescriptor) : this(domainDescriptor, DynamicTypeGenerator.Instance)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DomainModelService"/> class.
/// </summary>
/// <param name="domainDescriptor">The <see="DomainModelDescriptor"> that this service has to represent.</param>
/// <param name="dynamicTypeGenerator">The type genrator to use for generating the types</param>
public DomainModelService(DomainModelDescriptor domainDescriptor, IDynamicTypeGenerator dynamicTypeGenerator)
{
_dynamicTypeGenerator = dynamicTypeGenerator;
_entityToTypeMap = GenerateTypes(domainDescriptor);
}
/// <summary>
/// Initializes the configuration for the service instance
/// </summary>
/// <param name="config">Configuration settings for the service.</param>
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.UseVerboseErrors = true;
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
config.DataServiceBehavior.AcceptProjectionRequests = true;
}
/// <summary>
/// Gets instance of an <see cref="IDynamicDataServiceMetadataProvider"/> derived type for this odata service.
/// </summary>
/// <returns>An instance of an <see cref="DynamicDataServiceMetadataProvider"/></returns>
public override IDynamicDataServiceMetadataProvider GetMetadataProvider()
{
DynamicDataServiceMetadataProvider metaDataProvider = new DynamicDataServiceMetadataProvider(AssemblyName, "DomainModelService");
_resourceSetToTypeMap = new Dictionary<ResourceSet, Type>();
foreach (var entity in _entityToTypeMap.Keys)
{
Type t = _entityToTypeMap[entity];
var resourceType = new ResourceType(t, ResourceTypeKind.EntityType, null, AssemblyName, entity.EntityName, false);
foreach (var property in entity.EntityProperties)
{
ResourcePropertyKind kind = ResourcePropertyKind.Primitive;
if (property.IsIdentity)
{
kind |= ResourcePropertyKind.Key;
}
var resourceProperty = new ResourceProperty(property.Name,
kind, ResourceType.GetPrimitiveResourceType(property.Type));
resourceType.AddProperty(resourceProperty);
}
metaDataProvider.AddResourceType(resourceType);
ResourceSet resourceSet = new ResourceSet(String.Format("{0}s",entity.EntityName), resourceType);
metaDataProvider.AddResourceSet(resourceSet);
_resourceSetToTypeMap.Add(resourceSet, t);
}
return metaDataProvider;
}
/// <summary>
/// Gets instance of an <see cref="IDataServiceMetadataProvider"/> derived type for this odata service.
/// </summary>
/// <param name="metadataProvider">The <see cref="IDataServiceMetadataProvider"/></param>
/// <returns>An instance of an <see cref="DynamicDataServiceQueryProvider"/></returns>
public override IDataServiceQueryProvider GetQueryProvider(IDataServiceMetadataProvider metadataProvider)
{
return new DynamicDataServiceQueryProvider(metadataProvider);
}
/// <summary>
/// Create the underlying data source for this odata service
/// </summary>
/// <returns>An instance of an <see cref="DynamicDataContext"/> class</returns>
protected override DynamicDataContext CreateDataSource()
{
return new DynamicDataContext(_resourceSetToTypeMap,
ConfigurationManager.ConnectionStrings[DomainModelServiceSettings.Current.TargetStoreConnectionStringName].ConnectionString);
}
/// <summary>
/// Generates types dynamically that represent the <see cref="EntityDescriptor"/> entities of the <paramref name="domainDescriptor"/>
/// </summary>
/// <param name="domainDescriptor">The <see cref="DomainModelDescriptor"/> that the types are to be generated for.</param>
/// <returns>A <see cref="Dictionary"/> that contains mapping between an <see cref="EntityDescriptor"/> and the generated type</returns>
private Dictionary<EntityDescriptor, Type> GenerateTypes(DomainModelDescriptor domainDescriptor)
{
Dictionary<EntityDescriptor, Type> map = new Dictionary<EntityDescriptor, Type>();
AssemblyBuilder assemblyBuilder = null;
ModuleBuilder moduleBuilder = null;
_dynamicTypeGenerator.CreateDynamicAssembly(AssemblyName, out assemblyBuilder, out moduleBuilder);
foreach (var entity in domainDescriptor.Entities)
{
map.Add(entity, _dynamicTypeGenerator.CreateLinqToSqlType(AssemblyName, assemblyBuilder, moduleBuilder, entity));
}
if (DomainModelServiceSettings.Current.StoreGeneratedAssemblies)
{
assemblyBuilder.Save(String.Format("{1}.dll", domainDescriptor.Version, AssemblyName));
}
return map;
}
}
The service requires an instance of a
DomainModelDescriptor class to be provided when the service is constructed. The
DomainModelDescriptor defines the domain model entities and their properties that are to be exposed as an OData service. The constructor generates a set of types that map to the
EntityDescriptor types that are contained within the
DomainModelDescriptor instance.
When the OData framework invokes the on the
object GetService(Type serviceType) method of the
IServiceProvider interface this in turn invokes the two overridden methods
GetMetadataProvider() and
GetQueryProvider(IDataServiceMetadataProvider metadata) methods.
Of the two methods only the
GetMetadataProvider() is of interest. This method creates an instance of a
DynamicDataServiceMetadataProvider type and populates it with the required
ResourceSet,
ResourceTypes,
ResourceProperty values that will enable the OData framework to describe the underlying data source structure to an OData client.
Another key method is the
CreateDataSource() method that creates the underlying data source for the OData service. In this implementation the data source is an instance of a
DynamicDataContext. The
DynamicDataServiceQueryProvider and
DynamicDataServiceUpdateProvider types both leverage the functionality of the
DynamicDataContext provider to perform their function.
DynamicDataContext
The
DynamicDataContext class is a simple Linq To Sql
DataContext derived class that is responsible for marshalling calls between the Custom Data Service Provider components and the underlying data store.
public class DynamicDataContext : DataContext, IDynamicDataContext
{
private Dictionary<ResourceSet, Type> _resourceSetToTypeMap;
/// <summary>
/// Initializes a new instance of the <see cref="DynamicDataContext"/> class
/// </summary>
/// <param name="resourceSetToTypeMap">A <see cref="Dictionary"/> that contains a mapping from a <see cref="ResourceSet"/> to an underlying type</param>
/// <param name="connection">The connection string for this <see cref="DataContext"/> derived type to use</param>
public DynamicDataContext(Dictionary<ResourceSet, Type> resourceSetToTypeMap, String connection) : base(connection)
{
_resourceSetToTypeMap = resourceSetToTypeMap;
}
/// <summary>
/// Gets an instance of an <see cref="IQueryable"/> for the <paramref name="resourceSet"/>
/// </summary>
/// <param name="resourceSet">The resource set to retrieve the <see cref="IQueryable"/> instance for</param>
/// <returns>
/// An instance of an <see cref="IQueryable"/> type
/// </returns>
public IQueryable GetQueryableForResourceSet(ResourceSet resourceSet)
{
Type t = _resourceSetToTypeMap[resourceSet];
return GetTableForType(t);
}
/// <summary>
/// Add a resource to the underlying store
/// </summary>
/// <param name="resource">The resource to add to the store</param>
public void AddResource(object resource)
{
ITable table = GetTableForType(resource.GetType());
IEntity baseType = (IEntity) resource;
if (baseType.Identifier == Guid.Empty)
{
baseType.Identifier = Guid.NewGuid();
}
table.Attach(resource);
}
/// <summary>
/// Remove a resource from the underlying store
/// </summary>
/// <param name="resource">The resource to remove</param>
public void RemoveResource(object resource)
{
ITable table = GetTableForType(resource.GetType());
table.DeleteOnSubmit(resource);
}
/// <summary>
/// Commit changes to the underlying storeB
/// </summary>
public void CommitChanges()
{
this.SubmitChanges();
}
private ITable GetTableForType(Type type)
{
MethodInfo method = typeof(DataContext).GetMethod("GetTable", Type.EmptyTypes);
MethodInfo generic = method.MakeGenericMethod(type);
return (ITable) generic.Invoke(this, null);
}
}
As you can see there is nothing particularly complex about the DynamicDataContext. The
GetQueryableForResourceSet simply maps
ResourceSet to a type to provide an
IQueryable object for querying data, which the OData framework will expose. When modifying or deleting entities in the data store the
GetTableForType method uses some simple reflection to get the underlying
ITable derived object to which changes are submitted.
Now we have a OData service that can be dynamically configured based on metadata. In the next post,
Part 4 – WCF Service Host, I will cover the activation and instantiation of a DomainModelService.