Skip to main content

Custom Data Service Providers

Published: April 2011

Microsoft Corporation


If you have decided that working with Microsoft’s WCF Data Services Framework is the best means to create a data service that supports OData, the next step is to determine whether you can use one of the framework’s built-in Data Service Providers or if you need to implement your own.

Note: using a custom data service provider requires the WCF (ADO.NET) Data Services Update for .NET Framework 3.5 SP1 released in February 2010 or the .NET Framework version 4.

What is a Data Service Provider?

A Data Service Provider is simply a .NET class that sits between the Data Services Framework and the underlying data source that’s being exposed. At a minimum, this class should implement the IDataServiceQueryProvider and IDataServiceMetadataProvider interfaces, which provide the basis for a read-only service; it generally also implementsIServiceProvider through which the data services framework hooks everything together.

Optionally, the provider may also implement IDataServiceUpdateProvider, IDataServicePagingProvider, and IDataServiceStreamProvider. The purpose of all these are described in Table 1. (Note: the links given here for these interfaces point to the .NET Framework 4 documentation but these also exist in the Data Services Update for .NET Framework 3.5 SP1.)

InterfaceUse by WCF Data Services Framework
IServiceProviderInforms the framework that the class is an appropriate service.
IDataServiceQueryProviderUse in conjunction with all HTTP GET, PUT, POST, and DELETE requests.
IDataServiceMetadataProviderUsed to reason about available ResourceTypes, Properties, Keys, NavigationProperties, and ResourceSets.
IDataServiceUpdateProviderUsed to fulfill HTTP PUT, POST, and DELETE requests.
IDataServicePagingProviderAllows fine-grained control over server-driven paging.
IDataServiceStreamProviderUsed to manipulate an underlying stream for Media Link Entries.

Table 1: Interfaces implemented by a custom provider. Note that all of theIDataService* interfaces are defined in the System.Data.Services.Providers namespace;IServiceProvider is defined in the System namespace.

To fulfill its end of the bargain, the provider internally utilizes objects in the data store (or representing the data store) that implement the IQueryable interface (found in the System.Linq namespace).

Overall, these relationships are illustrated in Figure 1.

WCF Data Services and Data Service Providers

Figure 1: The WCF Data Services Framework interacts with a data source through a data service provider. The provider is a .NET class that implements IServiceProvider, IDataServiceQueryProvider and IDataServiceMetadataProvider, and optionallyIDataServiceUpdateProvider, IDataServicePagingProvider, and IDataServiceStreamProvider. The provider can be directly part of the data source, if desired.

Existing/Built-In Providers

A quick look at Figure 1 should convince you that implementing a full data service provider is a non-trivial investment, as we’ll see in this document. Let’s then take a quick look at providers that already exist so you can understand if they can be utilized in your particular scenario. (See http://msdn.microsoft.com/en-us/library/dd672591(VS.100).aspx for documentation on these providers.)

Entity Data Model/ADO.NET Entity Framework

If you are already employing the ADO.NET Entity Framework to create a data access layer to a source (for which there is already an ADO.NET data provider), the Data Services Framework can use your Entity Data Model. Simply derive your specific data service class from DataService<T> where T is your strongly-typedObjectContext from the Entity Framework, and you’re done.

For example, ifNorthwindEntities is the name of your Entity Framework ObjectContext class, then you would define a NorthwindDataService class as follows:

public class NorthwindDataService :DataService<NorthwindEntities>

For more details on this provider, see http://msdn.microsoft.com/en-us/library/dd673932(VS.100).aspx.

Reflection Provider

If you have your own class that represents a data source and it has a number of strongly-typedIQueryable properties, you can use the built-in reflection provider to turn it into a read-only service. For example, the following class has two queryable properties, Products and Categories:

public class MyDataSource
{
publicIQueryable<Product>Products {get {...} }
publicIQueryable<Categories>Categories {get {...} }
}

With this, the built-in reflection provider will infer resource sets, types, and properties automatically. This is best when you have static classes that are defined at runtime or are defined by a typed ADO.NET DataSet.

When using the reflection provider, you can also make the data source read-write by extending your data source class to implement IUpdatable (as shown in Figure 1):

public class MyDataSource : IUpdatable

As useful as this approach is, there are some limitations that might disqualify it for your particular scenario:

  1. It is static: the shape of the service cannot evolve over time.
  2. It needs CLR classes for each ResourceType, which you might not have.
  3. It needs to find ID or {Type}ID properties to use as keys. This requires use of the [DataServiceKey(...)] attribute.
  4. It blindly exposes all public properties of the CLR classes.
  5. You can’t take advantage of advanced features like Open Types, which allows resources to optionally include more Open Properties.
  6. You have less control; for example, you can't log requests as easily or modify metadata or rename properties.

If any of these limitations are a problem, then a custom provider is necessary.

LINQ to SQL

If you want to create a data service over a LINQ to SQL data source, take a look at the IUpdatable Implementation for LINQ to SQL on MSDN CodeGallery. This sample shows how to extend a strongly-typedDataContext class to implementIUpdatable. In conjunction with the reflection provider described above, you can then use thisDataContext just as if it were an Entity FrameworkObjectContext:

public class NorthwindDataService :DataService<NorthwindDataContext>

Typed vs. Untyped Data Providers

Though it is a non-trivial task, implementing a custom data provider is essential for data sources that don’t fit the standard molds covered by the other options above (a good example is SharePoint 2010). The remaining sections in this article will guide you through the process, starting with a read-only service and incrementally adding optional features.

There are two main varieties of providers that we’ll cover here. The first, covered in Parts 1-3, is a “typed” data provider, which is backed by specific CLR classes for each type of resource that the provider exposes. An “untyped” provider, on the other hand is necessary when specific backing CLR classes don’t exist (as when working with a database, though general-purpose classes, such as a Dictionary, might be used in an untyped provider). We’ll cover specific considerations for untyped providers in Parts 4 and 5 based on the discussion of typed providers in Parts 1-3.

Besides not needing individual classes for each resource type, there are a few things that are easier to do in untyped providers:

  1. Resource types can change at runtime (such as adding a new property to the resource); it’s not necessary to recompile or restart the service. This means you can also dynamically evolve your data model to expose more resource types and data sets as they become available.
  2. You can store a pointer to the metadata for the resource type directly in the resource type itself.
  3. You can store extra data along with a resource very easily, which potentially allows you to take advantage of the WCF Data Services Framework’s support for OpenTypes and Open Properties.

At present, this document does not cover the above features, nor does it cover server-side paging (IDataServicePagingProvider)or streaming media (IDataServiceStreamProvider). For details on these, see the links in the Going Further section at the end.

Part 1: Creating a Basic Typed Data Service Provider

If you’d like to start first by looking at some code before we discuss it, we’ll be working with the series of samples that accompany this document in the Data Service Provider Toolkit. Each sample has the same structure but implements different feature levels as implied by the folder in which the sample lives: Typed\RO, Typed\RW, Typed\RWNavProp, UnTyped\RO, and Untyped\RW. We'll refer to specific samples by these folder names, and start here by looking at the projects in the Typed\RO folder:

  • ODataDemo is a small demonstration data service that works with a small in-memory data source:
    • The in-memory data source is specifically composed of three instances of the ProductEntity class.
    • The main data service class is DemoDSPDataServicewhich is derived from DSPDataService<DSPContext>whereDSPDataService is the custom provider class (see below) and DSPContext holds the data.
    • This data service only has three methods in its implementation:
      • CreateDataSource instantiates the in-memoryProductEntity objects.
      • CreateDSPMetadata is an implementation of an abstract method defined in the custom provider to initialize the service’s metadata.
      • InitializeService sets up access rules and other service-wide behaviors, as all data services do in this method.

  • DataServiceProvider is the implementation of the custom provider:
    • DSPDataService.cs contains the implementation of the DSPDataService class from which the actual instance of the data service is derived (see above). It’s derived fromDataService<T>, a base class provided by the WCF Data Services Framework. It also implementsIServiceProvider and contains two private objects, DSPMetadata andDSPResourceQueryProviderthat supply the other interfaces for a basic read-only service (see below).
    • DSPMetadata.cs contains the implementation of theDSPMetadata class, which implementsIDataServiceMetadataProvider.
    • DSPResourceQueryProvider.cs contains the implementation of the DSPResourceQueryProvider class, which implementsIDataServiceQueryProvider.
    • DSPContext.cs contains theDSPContext class, which holds the actual data reported through the provider. It’s implemented as a simple dictionary.
    • ResourceTypeAnnotations.cs and ResourcePropertyAnnotations.cs contain helper classes.

  • DSPTest is a test project for the data service using the custom provider. You can run these tests in Visual Studio using the Test | Run | All Tests in Solution menu command. The code in these projects will give good examples of different requests that exercise the features of the data service.

We’ll start first by looking at theIServiceProvider implementation, then those classes/interfaces for a read-only service, then the other interfaces that add optional features.

IServiceProvider

When the WCF Data Services Framework first takes a request, it of course needs to ensure that the service to which that request has been directed (in the URL) is, in fact, a suitable data service. It does so by looking for theIServiceProvider interface and using its GetService method to locate the implementations of IDataServiceMetadataProvider and IDataServiceQueryProvider.

The following code is the implementation for this found in DSPDataService.cs (DataServiceProvider project):

public abstract classDSPDataService<T> :DataService<T>,IServiceProvider where T : DSPContext
{ 
...
public object GetService(Type serviceType)
{
if (serviceType ==typeof(IDataServiceMetadataProvider))
{
// Note that we're calling the property Metadata here so that the metadata gets initialized the first
// time it's accessed.
return this.Metadata;
}
else if (serviceType ==typeof(IDataServiceQueryProvider))
{
return this.resourceQueryProvider;
}
else
{
return null;
}
}
...
}

How this method actually works depends upon your specific implementation, of course, but you can see the basics here nonetheless.

As noted before, the actual data service class in this project—the data service that is part of the overall web application defined by the solution—is derived from DSPDataService<T> as seen in the DemoDSPDataService.svc.cs file of the ODataDemoService project. A data service that does not employ a custom provider would otherwise be derived from the standard DataService<T> class provided by the framework.

It's important to also note that if WCF Data Services framework fails to findIServiceProvider through the above process, it will also look for it directly on the data source directly, that is, on the class represented by T. This is another way of saying that the data source can be the provider as well (merging those two pieces as shown in Figure 2).

The Data Source

In our sample, the DSPContext class is the actual data source that the custom provider is working with, that is, it’s the data type that we use with the various <T>’s. As such it can be called anything, and can be derived from anything.

Note that we’re using this data source in a strongly-typed way by adding a generic constraint to the definition of the provider class, specifically thewhere T : DSPContext at the end:

public abstract classDSPDataService<T> :DataService<T>,IServiceProvider where T : DSPContext

If the data source is fixed, however, and you don’t need a strongly-typed sub-class, then you wouldn’t need that constraint:

public class DSPDataService: DataService<MyDataSource>, IServiceProvider

IDataServiceMetadataProvider

The next step is to provide the implementation of IDataServiceMetadataProvider, which is responsible for describing the shape or “model” of the information in your data source. This metadata is surfaced in the form of ResourceType, ResourceProperty, ServiceOperation, ResourceSet and ResourceAssociationSet instances, all of which are public classes in the System.Data.Services.Providers namespace. You use these to build the metadata that’s returned through the methods of the interface.

How you implement those methods and construct the metadata depends on the nature of the data source. Your data might be static, in which case the metadata can be built once and returned whenever it’s requested. Or you might be dealing with completely dynamic data such that you’re creating the metadata from scratch every time (an obvious trade-off between flexibility and performance). It doesn’t really matter so long as you serve up the appropriate metadata.

The methods in the interface essentially just expose different aspects of the metadata. While the interface contains six methods and five properties, the basic (starter) implementation is fairly straightforward as we can see in the Typed\RO sample.

To look at the highlights of that sample, let’s first look back to the implementation of IServiceProvider.GetService that we saw earlier. When asked forIDataServiceMetadataProvider it simply returns an instance of DSPMetadata, which implements that interface and manages the metadata. For this purpose it contains two private Dictionary instances calledresourceSets and resourceTypes; for the most part the methods and properties of the interface are only a few lines each:

public class DSPMetadata : IDataServiceMetadataProvider
{
private Dictionary<string,ResourceSet> ResourceSets;
private Dictionary<string,ResourceType> resourceTypes;
private string containerName;
private string namespaceName;
...
public DSPMetadata(string containerName,string namespaceName)
{
this.ResourceSets =newDictionary<string,ResourceSet>();
this.resourceTypes =newDictionary<string,ResourceType>();
this.containerName = containerName;
this.namespaceName = namespaceName;
}
...
// Marks the metadata as read-only.
internal void SetReadOnly()
{
foreach (var type in this.resourceTypes.Values)
{
type.SetReadOnly();
}
foreach (var set in this.ResourceSets.Values)
{
set.SetReadOnly();
}
}
...
// IDataServiceMetadataProvider members
public string ContainerName
{
get { returnthis.containerName; }
}
public string ContainerNamespace
{
get { returnthis.namespaceName; }
}
public System.Collections.Generic.IEnumerable<ResourceType> GetDerivedTypes(ResourceType resourceType)
{
// We don't support type inheritance yet
return new ResourceType[0];
}
public ResourceAssociationSet GetResourceAssociationSet(ResourceSet ResourceSet, ResourceType resourceType,
ResourceProperty resourceProperty)
{
throw new NotImplementedException("Should never get here, reference properties are not yet supported.");
}
public bool HasDerivedTypes(ResourceType resourceType)
{
// We don't support type inheritance yet
return false;
}
public System.Collections.Generic.IEnumerable<ResourceSet> resourceSets
{
get { return this.resourceSets.Values; }
}
public System.Collections.Generic.IEnumerable<ServiceOperation> serviceOperations
{
get { return new serviceOperations[0]; }
}

}

public bool TryResolveResourceSet(string name, out ResourceSet resourceSet)

{

return this.ResourceSets.TryGetValue(name,out resourceSet); ;

}

public bool TryResolveResourceType(string name, out ResourceType resourceType)

{

return this.resourceTypes.TryGetValue(name,out resourceType);

}

public bool TryResolveServiceOperation(string name,out ServiceOperation serviceOperation)

{

// No service operations are supported yet

serviceOperation = null;

return false;

}

public System.Collections.Generic.IEnumerable<ResourceType> Types

{

get { returnthis.resourceTypes.Values; }

}

}

The other methods within DSPMetadata are internal helper functions to populate theresourceSets and resourceTypes. The populating itself happens through some interaction between the data service and the provider. If you remember from earlier, the provider (the DSPDataService<T> class) defined an abstract method calledCreateDSPMetadata that’s implemented within DemoDSPDataService. Here’s that code:

//From DSPDataService.cs in the DataServiceProvider project
public abstract classDSPDataService<T> :DataService<T>, IServiceProvider where T :DSPContext
{
private DSPMetadata metadata;
...
protected abstract DSPMetadata CreateDSPMetadata();
protected DSPMetadata Metadata
{
get
{
if (this.metadata == null)
{
this.metadata = CreateDSPMetadata();
this.metadata.SetReadOnly();
this.resourceQueryProvider =new DSPResourceQueryProvider(this.metadata);
}
return this.metadata;
}
}
...
}
...
//From DemoDSPDataService.cs in the ODataDemoService project, within the DemoDSPDataService class
protected override DSPMetadata CreateDSPMetadata()
{
DSPMetadata metadata = newDSPMetadata("DemoService","DataServiceProviderDemo");
// Rename the type to "Product"
ResourceType product = metadata.AddEntityType(typeof(ProductEntity),"Product");
metadata.AddKeyProperty(product, "ID");
metadata.AddPrimitiveProperty(product, "Name");
metadata.AddPrimitiveProperty(product, "Description");
// By not adding the Price property to metadata we're hiding it from the users of the service,
// there's no way users can even know that such property exists, let alone get its value.
metadata.AddPrimitiveProperty(product, "ReleaseDate");
metadata.AddPrimitiveProperty(product, "DiscontinueDate");
metadata.AddPrimitiveProperty(product, "Rating");
metadata.AddResourceSet("Products", product);
return metadata;
}

The methods being called on the metadata variable are the helper functions defined inDSPMetadata. They basically create ResourceType, ResourceTypeKind, ResourceProperty , ResourcePropertyKind, andResourceSet instances as needed. As you can see, all this is building up a description of theProductEntity type of which our actual data is composed:

public class ProductEntity
{
public int ID {get; set; }
public string Name {get; set; }
public string Description {get; set; }
public double Price {get; set; }
public DateTime ReleaseDate {get; set; }
public DateTime? DiscontinueDate { get; set; }
public int Rating {get; set; }
}

What’s most important to note here is that it’s not necessary to expose everything in the metadata. Properties that should remain private—likePrice in this example—simply aren’t added to the metadata, and thus no OData client on the other end of this whole scene will know about it let alone be able to retrieve values.

Also note that little call to metadata.SetReadOnly() within the DSPMetadata class’ Metadata property. This effectively freezes the metadata so it cannot be modified within the scope of the current HTTP REQUEST that’s being processed—a good thing! It’s worth noting too that the WCF Data Services Framework will ask for metadata for each REQUEST, so you have the opportunity to reconstruct it as necessary on a per-request basis.

Things will get a little more complicated as you add support for inheritance, relationships, and service operations (filling in some of the stubbed-out methods above).

Finally, you might have noticed that there are two kinds of functions/properties on the IDataServiceMetadataProvider interface: those that return enumerations and those that try to find a specific instance. This is done for performance reasons. Most requests to the service use the TryResolve* methods, meaning that even if you have a lot of metadata you can often avoid creating most of it. Those methods that return the whole smash, on the other hand, are used much more sparingly, as when a data service is asked for all its metadata with a$metadata in the URI.

IDataServiceQueryProvider

The last piece that will complete a basic custom provider is an implementation of IDataServiceQueryProvider. This is already surfaced through the provider’s IServiceProvider.GetService implementation, which returns an instance of theDSPResourceQueryProvider class (it’s also created through theMetadata property we just saw):

 

public abstract classDSPDataService<T> :DataService<T>, IServiceProvider where T :DSPContext
{
...
private DSPResourceQueryProvider resourceQueryProvider;
...
public object GetService(Type serviceType)
{
if (serviceType ==typeof(IDataServiceMetadataProvider))
{
return this.Metadata;
}
else if (serviceType ==typeof(IDataServiceQueryProvider))
{
return this.resourceQueryProvider;
}
else
{
return null;
}
}
}

DSPResourceQueryProvider is derived from IDataServiceQueryProvider, of course, implementing the six methods and two properties of that interface (DSPResourceQueryProvider.cs in the DataServiceProvider project; comments omitted for brevity):

internal class DSPResourceQueryProvider : IDataServiceQueryProvider
{
private DSPContext dataSource;
private DSPMetadata metadata;
public DSPResourceQueryProvider(DSPMetadata metadata)
{
this.metadata = metadata;
}
//IDataServiceQueryProvider Members
public objectCurrentDataSource
{
get
{
return this.dataSource;
}
set
{
if (this.dataSource != null)
{
throw new InvalidOperationException("CurrentDataSource should only be set once.");
}
this.dataSource = (DSPContext)value;
}
}
public object GetOpenPropertyValue(object target, string propertyName)
{
throw new NotSupportedException("Open types are not yet supported.");
}
public IEnumerable<KeyValuePair<string,object>> GetOpenPropertyValues(object target)
{
throw new NotSupportedException("Open types are not yet supported.");
}
public object GetPropertyValue(object target, ResourceProperty resourceProperty)
{
return resourceProperty.GetAnnotation().InstanceProperty.GetValue(target,null);
}
private System.Linq.IQueryable GetTypedQueryRootForResourceSet<TElement>(ResourceSet resourceSet)
{
return this.dataSource.GetResourceSetEntities(resourceSet.Name).Cast<TElement>().AsQueryable();
}
public System.Linq.IQueryable GetQueryRootForResourceSet(ResourceSet resourceSet)
{
// We can't return the IList.AsQueryable directly as that would return in fact IQueryable<object>.
// Currently WCF Data Services don't support that as it is required that the returned IQueryable<T> has T
// equal to the instance type of the base type of the resource set.
// So we need to Cast the returned collection first.
// Use a helper method to do this for us.
MethodInfo getTypedQueryRootForResourceSetMethod =
typeof(DSPResourceQueryProvider).GetMethod(
"GetTypedQueryRootForResourceSet",
BindingFlags.NonPublic | BindingFlags.Instance);
return (IQueryable)getTypedQueryRootForResourceSetMethod.MakeGenericMethod(
ResourceSet.ResourceType.InstanceType).Invoke(this,object[] { resourceSet } );
}
public ResourceType GetResourceType(object target)
{
Type targetType = target.GetType();
// (A rather slow/linear search of resource types)
return this.metadata.Types.Single(rt => rt.InstanceType == targetType);
}
public object InvokeServiceOperation(ServiceOperation serviceOperation, object[] parameters)
{
throw newNotSupportedException("Service operations are not yet supported.");
}
public bool IsNullPropagationRequired
{
//This is so WCF Data Services can compensate for the lack of NullPropagation in LINQ to Objects
get { return true; }
}

}

As with IDataServiceMetadataProvider, most of these methods are one-line implementations. Indeed, if you only want to expose a service document and support $metadata in the URI, you only need implement theCurrentDataSource property; the rest of the methods are only called when data is actually queried. (In other words, replace all the other method code above with throw new NotSupportedException(...) and you can start there.)

Of course, most data sources will want to support queries as well, so let’s go into this whole mechanism a little more deeply.

The big job at hand is to translate the OData URI conventions that express query operations, such as $filter, into queries that the underlying data source actually understands. The WCF Data Services Framework, then, needs to describe that URI-based query to the underlying provider in some generalized language that is appropriate for both relational and non-relational data sources. For this, the framework has chosen to use LINQ expressions, especially because there are already many existing implementation of “query providers” that can handle LINQ expressions for a wide variety of data sources.

LINQ expressions are built up in an instance of theIQueryable interface (from the System.Linq namespace), which holds in itself an expression tree to represent the query. (It then relies on an implementation of IQueryProvider to actually execute it). Thus for the WCF Data Services Framework to build a query from the information it’s parsing from the URI, it asks the data provider for anIQueryable. This is the purpose of the main IDataServiceQueryProvider method in the code above, namely GetQueryRootForResourceSet, returns an IQueryable.

How your provider gets hold of an appropriateIQueryable is, of course, an internal implementation detail. In the case of the Typed\RO sample, instances ofIQueryable can be easily obtained from the data source class, DSPContext. To be specific, GetQueryRootForResourceSet, through some massaging to get the type set up properly, calling to the private helper GetTypedQueryRootForResourceSet. This helper callsDSPContext.GetResourceSetEntities which returns an appropriate List that can then be converted into an IQueryableobject using the AsQueryable method:

public class DSPContext
{
private Dictionary<string,List<object>> ResourceSetsStorage;
public DSPContext()
{
this.ResourceSetsStorage =new Dictionary<string,List<object>>();
}
public IList GetResourceSetEntities(string ResourceSetName)
{
List<object> entities;
if (!this.ResourceSetsStorage.TryGetValue(ResourceSetName,out entities))
{
entities = new List<object>();
this.ResourceSetsStorage[ResourceSetName] = entities;
}
return entities;
}
}

We get away with this in this sample because we have strongly-typed, in-memory data (as shown in DemoDSPDataService.CreateDataSource) for which the .NET Framework can provide IQueryable. As we’ll see in Part 4, working with an untyped data source requires more work in this department, but for now we at least have a grasp of the basic mechanics and have, in hand, a fully functional query provider.

An important detail to point out is something you see in the implementation of theDSPMetadata class (DSPMetadata.cs), specifically in its AddEntityType, AddComplexType, AddProperty, and AddComplexProperty methods. In these you’ll see flags calledCanReflectOnInstance set totrue, which is how the provider, through the metadata, tells the WCF Data Services Framework that it’s strongly typed. Because of this, the framework knows that it can get values directly from resource objects via reflection and so it will never actually callIDataServiceQueryProvider.GetPropertyValue. Again, we’ll see that this changes in the untyped scenario.

Query Interaction Psuedo-Code

To review and deeper our understanding of how the framework interacts with the interfaces we’ve implemented thus far, the following pseudo-code shows the basic process for handling an HTTP GET request:

// Locate the service provider
var dataservice = new DataService<T>;
IServiceProvider sp = dataservice as IServiceProvider;
if (sp ==null)
{
// Not really a data service with a custom provider
}
// Get the various provider interfaces
IDataServiceMetadataProvider mdp = sp.GetService(typeof(IDataServiceMetadataProvider));
IDataServiceQueryProvider qp = sp.GetService(typeof(IDataServiceQueryProvider));
// Set the CurrentDataSource (if necessary)
if (qp.CurrentDataSource ==null)
qp.CurrentDataSource = dataservice.CreateDataService();
// Find the Products ResourceSet
var ResourceSet = null;
// The "Products" text here would come from the URI, and not be hard-coded
if (!mdp.TryResolveResourceSet("Products",out ResourceSet))
thrownew Exception("404");
// Get the queryable for the ResourceSet
IQueryable queryRoot = qp.GetQueryRootForResourceSet(ResourceSet);
// Compose expressions onto the IQueryable to represent $filter, $select etc. in the URI
queryRoot = Compose(options, queryRoot);
// Start writing response
WriteStartODataFeed();
// Enumerate results
foreach (object resource in queryRoot)
{
// Get the ResourceType for resource. NOTE: because of inheritance it might be a resourceType
// derived from ResourceSet.ResourceType
ResourceType type = qp.GetResourceType(type);
WriteResource(resource,type);
}
WriteEndODataFeed();

Where We Are

At this point we have a complete read-only data service. If you compile and run the project, it should start up an ASP.NET Development Server and show a browser like this, referencing DemoDSPDataService.svc in the URI:

The default service document for the sample data service

Figure 2: The default service document for the sample data service.

If you add $metadata to the URI you should see the following:

The metadata returned for the sample data service.

Figure 3: The metadata returned for the sample data service.

If you append Products instead, then you’ll see the individual items.

The Products entity data returned by the sample data service.

Figure 4: The Products entity data returned by the sample data service.

Filtering operations are also supported with the code we’ve seen thus far, if you’d like to give them a try. Adding ?$filter=Name eq ‘Bread’ to the URI (after Products in the preceding example), shows only those items in the Products collection that match.

Data returned by a query to the sample data service.

Figure 5: Data returned by a query to the sample data service.

Part 2: Supporting Updates

We’re now ready to start adding additional features to our basic read-only data service. We’ll start with adding update capabilities to create a read-write data service. This is mostly a matter of implementingIDataServiceUpdateProvider, which is a derivative of IUpdatable. (This implies that if you already have an IUpdatable you need to expose it via IDataServiceUpdateProvider to make it work with the WCF Data Services Framework.)

The context for this section is the Typed\RW sample, which is the same as the Typed\RO solution with this added feature. The additions to Typed\RW are as follows:

  • DataServiceProvider project:
    • The implementation ofIServiceProvider.GetService now also checks forIDataServiceUpdateProvider and returns an instance of the class DSPUpdateProvider.
    • Contains a new file, DSPUpdateProvider.cs, that implements DSPUpdateProvider.
    • The implementation ofIDataServiceMetadataProvider has a little added code.

  • DemoDSPDataService project:
    • The internal CreateDataSource method in DemoDSPDataService.svc.cs adds a session to theDSPContextso changes are only applied to the current browser session. This is appropriate for an in-memory data source as we’re working with.
    • The InitializeService method sets open read-write access rights where it formerly set read-only rights.

In this section we’ll be focusing only on the code in the DataServiceProvider project as the rest is rather self-explanatory.

It’s helpful to note here that theDSPContext class, which manages our underlying data source, is inherently updatable: as a Dictionary that contains List instances, any of it can be updated and modified at any time. Of course, other data sources may not be so straightforward, so it’s a worthwhile step to think through the implications of insert, update, and delete operations where your particular source is concerned. In other words, you may need to do a little work on those source classes before you can wire things together throughIDataServiceUpdateProvider.

Implications of Batching

Another consideration is that of batching. IDataServiceUpdateProvider is inherently designed for this, meaning that a client can update many resources in a single request (that is, one HTTP PUT operation).

Updates happen through any number of calls toIDataServiceUpdateProvider.SetValue (or other data-changing methods) followed by a call toIDataServiceUpdateProvider.SaveChanges. When updating a single resource, there will be a SetValue call for each changed property of the resource prior toSaveChanges. Batching simply means that there may be SetValue calls for properties on multiple resources at once, all prior toSaveChanges. But it also means that everything before that final call is considered an atomic transaction and must be rolled back if there is an error anywhere in the process.

The implication of all this is that you cannot just immediately apply every change as they come; you need to record those changes in a temporary place and only commit them as a group upon SaveChanges. They should also be recorded sequentially so that a resource can be created, for example, before values are set within it.

If your data resides in a database, this typically means recording a series of commands and issuing them as a group.

With an in-memory data source like we’re working with in the Typed\RW sample, however, we have to keep such a record ourselves. This is accomplished through aList<Action> variable named pendingChanges, found in DSPUpdateProvider.cs. Every change made to our resources—through theCreateResource, DeleteResource,ResetResource, and SetValue methods—only adds an appropriate action to this list. SaveChanges then iterates through the list and applies those changes. We’ll see the code shortly, and keep in mind that this is just an implementation detail that will vary between providers and data sources, as necessary.

IDataServiceUpdateProvider

As mentioned earlier, the IDataServiceUpdateProvider interface is derived fromIUpdatable; in fact,IDataServiceProvider only adds a single method, SetConcurrencyValues, that isn’t even implemented in the Typed\RW sample. So what we’re really talking about here are the methods of IUpdatable. (For more details on SetConcurrencyValues, see the IDataServiceUpdateProvider documentation.)

First, here’s the first part of the DSPUpdateProvider class (DSPUpdateProvider.cs) that implements the interface, so you can see the declaration of the pendingChanges variable. The implementation of the IUpdatable.ClearChanges method is very simple as it just clears thependingChanges list;IUpdatable.SaveChanges, as noted earlier, just goes through that list and applies the changes.

public class DSPUpdateProvider : IDataServiceUpdateProvider
{
private DSPContext dataContext;
private DSPMetadata metadata;
// List of pending changes to apply once SaveChanges is called.
private List<Action> pendingChanges;
public DSPUpdateProvider(DSPContext dataContext, DSPMetadata metadata)
{
this.dataContext = dataContext;
this.metadata = metadata;
this.pendingChanges = new List<Action>();
}
...
public void ClearChanges()
{
// Simply clear the list of pending changes
this.pendingChanges.Clear();
}
public void SaveChanges()
{
// Just run all the pending changes we gathered so far
foreach (var pendingChange in this.pendingChanges)
{
pendingChange();
}
this.pendingChanges.Clear();
}
...
}

This leaves us to look at the interesting work that’s happening in the core methods of the interface as described in Table 2:

MethodPurpose
CreateResourceCreates new entity types on insert as well as new complex types on insert and update requests (handling HTTP POST, PUT, and MERGE).
DeleteResourcePerforms a deletion (handling HTTP DELETE).
GetResourceRetrieves a given resource.
GetValueRetrieves the value of a property within a resource.
ResetResourceResets the values in a resource to its default values.
SetValueChanges the value of a property within a resource.

Table 2: The core methods of theIDataServiceUpdateProvider interface.

Just to note, the AddReferenceToCollection, RemoveReferenceFromCollection, and SetReference methods ofIUpdatable are not implemented in the Typed\RW sample, along IDataServiceUpdateProvider.SetConcurrencyValues. These are used to support relationships and ETags, which we’ll discuss later. For now we can ignore these methods.

Also, IUpdatable.RevolveResource is a trivial implementation in this code. Generally speaking, the methods likeCreateResource return an abstract handle to a resource (type object);RevolveResource then translates between a handle and the actual resource itself. This allows for a level of indirection wherein you might use proxies to maintain a change list (see the section on GetResource below). In this sample, though, we use the resources themselves as the handles so there’s no translation needed.

Let’s look now at each of the interesting methods in turn.

CreateResource

CreateResource is invoked when an OData client sends an HTTP POST, PUT or MERGE request for various insertions and updates. As you can see in the code, CreateResource is only given the name of the resource set (containerName in the code below) in which the new resource will live, and the name of a type. What this means is that CreateResource just creates a new resource with default values and adds it to the set (as will be processed by SaveChanges); if the WCF Data Services Framework has more specific values to place in this resource, it will follow up with calls to SetValue. For this reason it’s again imperative that your change-tracking implementation is sequential.

It is possible that CreateResource is called with containerName set to null. This means that a complex resource is being created and all we have to do is create an instance of the type and return it, in which case we don’t add any action to our list of pending changes.

In this implementation, you can see that we’re first making sure the type is valid by checking it against our metadata (which knows all that sort of stuff). We then create a resource of the type, and if containerName is null we just return it. Otherwise we add an action topendingChanges that will add the new resource to its container.

public object CreateResource(string containerName, string fullTypeName)
{
ResourceType resourceType;
if (!this.metadata.TryResolveResourceType(fullTypeName,out resourceType))
{
throw new ArgumentException("Unknown resource type '" + fullTypeName + "'.");
}
// Create new instance of the underlying CLR type (this will set all properties to defaults)
object newResource = Activator.CreateInstance(resourceType.InstanceType);
if (containerName != null)
{
// We're creating an entity and should add it to the resource set
// This check is just for documentation; this should never be called with non-entity type in this case.
if (resourceType.ResourceTypeKind != ResourceTypeKind.EntityType)
{
throw new ArgumentException("The specified resource type '"
+ fullTypeName + "' is not an entity type, but resource set was specified.");
}
IList resourceSetList = this.dataContext.GetResourceSetEntities(containerName);
// And register pending change to add the resource to the resource set list
this.pendingChanges.Add(() =>
{
resourceSetList.Add(newResource);
});
}
else
{
// We're creating a complex type instance, so no additional operation is needed.
// This check is just for documentation; should never be called with non-complex type in this case.
if (resourceType.ResourceTypeKind != ResourceTypeKind.ComplexType)
{
throw new ArgumentException("The specified resource type '"
+ fullTypeName + "' is not a complex type.");
}
}
// The method should return the resource "handle", we don't have handles so return the resource directly.
return newResource;
}

Note the last comment above. The different methods in this interface are designed to work with handles or proxies that represent the resource (in the code here we're simply using the resource itself as that handle). This allows for an extra layer of indirection so that an intermediate proxy could maintain, for example, a copy of the data being manipulated (like an ADO.NET DataSet).

DeleteResource

DeleteResource is given the handle to the resource to delete, which in our implementation is the resource itself. In response, we first determine which set the resource belongs to, then add an action to pendingChangesto affect the removal when SaveChanges is called.

public void DeleteResource(object targetResource)
{
// Get the resource type of the resource specified so that we know which resource set it belongs to
Type instanceType = targetResource.GetType();
ResourceType resourceType;
if (!this.metadata.TryResolveResourceType(instanceType,out resourceType))
{
throw new ArgumentException("Unknown resource type for instance type '"
+ instanceType.ToString() +"'.");
}
ResourceSet ResourceSet = resourceType.GetAnnotation().ResourceSet;
IList resourceSetList = this.dataContext.GetResourceSetEntities(ResourceSet.Name);
// Add a pending change to remove the resource from the resource set
this.pendingChanges.Add(() =>
{
resourceSetList.Remove(targetResource);
});
}

GetResource

When the WCF Data Services Framework tries to update or delete a resource, it first needs to retrieve that resource or a representation of it. This is whereGetResource comes into play.

The method is interesting in that it’s given a query (IQueryable) to identify the resource. This query should only identify a single resource; it should fail if zero or multiple results come out of the query, which is the purpose of the foreach statement in this code. Beyond that, we check to make sure the types are valid, then return the resource if all is well.

Note again that this method is specified to return a handle, allowing for a level of indirection so you can employ proxies or whatnot. (Such implementations, though, would avoid the use of Actions as we're using in this code.)

public object GetResource(System.Linq.IQueryable query, string fullTypeName)
{
// Since we're not using resource handles we're going to return the resource itself.
object resource = null;
foreach (object r in query)
{
if (resource != null)
{
throw new ArgumentException(
String.Format("Invalid Uri specified. The query '{0}' must refer to a single resource",
query.ToString()));
}
resource = r;
}
if (resource != null)
{
if (fullTypeName != null)
{
ResourceType resourceType;
if (!this.metadata.TryResolveResourceType(fullTypeName,out resourceType))
{
throw new ArgumentException("Unknown resource type '" + fullTypeName + "'.");
}
if (resource.GetType() != resourceType.InstanceType)
{
throw new System.ArgumentException(
String.Format("Invalid uri specified. ExpectedType: '{0}', ActualType: '{1}'",
fullTypeName, resource.GetType().FullName));
}
}
return resource;
}
return null;
}

GetValue

GetValue is a simple method to retrieve a named property from a given resource (thetargetResource is specified as a handle, so you’ll always want to do the appropriate conversion). As withGetResource, this method exists to provide a level of indirection, giving you more control over how you implement your change list.

public object GetValue(object targetResource,string propertyName)
{
// Note that since our resource property name does not necessarily have to match the name of
// the instance CLR property we need to find the resource property specified by the
// propertyName and use its instance property to access the CLR property on the resource.
// Get the resource type of the resource specified
Type instanceType = targetResource.GetType();
ResourceType resourceType;
if (!this.metadata.TryResolveResourceType(instanceType,out resourceType))
{
throw new ArgumentException("Unknown resource type for instance type '"
+ instanceType.ToString() +"'.");
}
// Find the property of the specified name on it
ResourceProperty resourceProperty = resourceType.Properties.FirstOrDefault(rp => rp.Name == propertyName);
if (resourceProperty == null)
{
throw new ArgumentException("Unknown resource property '"
+ propertyName + "' on resource type '" + resourceType.FullName + "'.");
}
// Simply use reflection to get the value of the property
return resourceProperty.GetAnnotation().InstanceProperty.GetValue(targetResource, null);
}

ResetResource

Just as CreateResource creates a new resource with default values,ResetResource reverts all values in a resource to those same defaults. It's used to handle PUT requests to an entity.

ResetResource can do this either by modifying the existing resource or by simply creating a new one and returning a handle to it. In the latter case, this method must also replace the old resource with the new one in its resource set and patch up any references to the old.

Note also that returned resource must have the same identity as the one on the input, meaning that its “key” properties must all have the same values.

As with updating other values, resetting a resource means adding an action to the pending change list.

public object ResetResource(object resource)
{
// Get the resource type of the resource specified
Type instanceType = resource.GetType();
ResourceType resourceType;
if (!this.metadata.TryResolveResourceType(instanceType,out resourceType))
{
throw new ArgumentException("Unknown resource type for instance type '"
+ instanceType.ToString() +"'.");
}
// We are going to create a new resource and then set all the properties on the existing
// resource to the values of those properties on the newly created resource. This is to
// allow constructors for the resources to specify default values for the properties.
// We are not going return a different resource instance, so no need to replace the resource or such.
object newResource = Activator.CreateInstance(resourceType.InstanceType);
this.pendingChanges.Add(() =>
{
foreach (var resourceProperty in resourceType.Properties)
{
// We will only copy non-key properties as we have to preserve the value of the key properties.
if ((resourceProperty.Kind & ResourcePropertyKind.Key) != ResourcePropertyKind.Key)
{
object propertyValue = resourceProperty.GetAnnotation()
.InstanceProperty.GetValue(newResource, null);
resourceProperty.GetAnnotation().InstanceProperty.SetValue(resource, propertyValue, null);
}
}
});
return resource;
}

SetValue

Finally, SetValue sets the value of a named property within a given resource (whose handle is intargetResource). This is, as you would expect, the workhorse method for most updates, and you can expect that updating a resource will generate multiple calls here. Each change, of course, is added to the pending change list.

public void SetValue(object targetResource,string propertyName, object propertyValue)
{
// Note that since our resource property name does not necessarily have to match the name of
// the instance CLR property we need to find the resource property specified by the propertyName
// and use its instance property to access the CLR property on the resource.
// Get the resource type of the resource specified
Type instanceType = targetResource.GetType();
ResourceType resourceType;
if (!this.metadata.TryResolveResourceType(instanceType,out resourceType))
{
throw new ArgumentException("Unknown resource type for instance type '"
+ instanceType.ToString() +"'.");
}
// Find the property of the specified name on it
ResourceProperty resourceProperty = resourceType.Properties.FirstOrDefault(rp => rp.Name == propertyName);
if (resourceProperty == null)
{
throw new ArgumentException("Unknown resource property '" + propertyName
+ "' on resource type '" + resourceType.FullName + "'.");
}
// Add a pending change to modify the value of the property
this.pendingChanges.Add(() =>
{
resourceProperty.GetAnnotation().InstanceProperty.SetValue(targetResource, propertyValue, null);
});
}

Give It a Try

With insert, update, and delete support now working, you can give the data service another go through a client program that can send the appropriate HTTP request. You can’t do this directly in the browser, however, because typing a URI in the address bar will only generate HTTP GET requests.

You can, however, follow some of the code in the DSPTest project. As noted before, this project contains test code that is invoked through Visual Studio's Test | Run | All Tests in Solution menu command. In the Typed\RW solution, you'll see that this project has an UpdateTests.cs code file that calls methods on aDataServiceContext object to do insertions, updates, and deletes against the data service. These calls, of course, generate the necessary HTTP requests.

Part 3: Supporting Relationships

To this point we’ve implemented a read-write typed provider, but it’s still a little simplistic because it doesn’t support relationships (or “navigation properties” in Entity Framework parlance).

To demonstrate this, we’ll be adding another entity type to our sample, specifically a class called CatalogEntity. The code for this variation can be found in the Typed\RWNavProp sample. Compared with the Typed\RW sample, the changes are as follows:

  • DemoDSPDataService project:
    • Adds a new CategoryEntity class and a relationship between a product and a category.
    • Describes the new entity in the metadata.
  • DataServiceProvider project:
    • In the DSPMetadata class that implements IDataServiceMetadataProvider, we now implement the GetResourceAssociationSet method of the interface.
    • In the DSPUpdateProvider class that implementsIDataServiceUpdateProvider, we now implement the AddReferenceToCollection,RemoveReferenceFromCollection, andSetReference methods.

Added Code in DemoDSPDataService.svc.cs

Before getting into the details of the provider, here are the bits of code at the service level that add this new entity type and its associated metadata. All of this is found in DemoDSPDataService.svc.cs.

First, the CategoryEntity class and an added Category field within ProductEntity:

public class ProductEntity
{
...
public CategoryEntity Category {get; set; }
}
public class CategoryEntity
{
public CategoryEntity()
{
this.Products = new List<ProductEntity>();
}
public int ID {get; set; }
public string Name {get; set; }
public List<ProductEntity> Products { get; set; }
}

The CreateDataSource method also creates a little initial category data for this class as well as theCategory field withinProductEntity. Note that the implementation of DSPContext.GetResourceSetEntities creates the Categories list if it doesn’t exist; no changes to DSPContext (the data source) are necessary for supporting these associations. In fact, because the query provider code (DSPResourceQueryProvider.cs) just works with the names of resource sets generally,Categories becomes automatically available to queries as well.

IList categories = context.GetResourceSetEntities("Categories");
CategoryEntity food =new CategoryEntity();
food.ID = 0;
food.Name = "Food";
categories.Add(food);
CategoryEntity beverages =new CategoryEntity();
beverages.ID = 1;
beverages.Name = "Beverages";
categories.Add(beverages);
bread.Category = food;
food.Products.Add(bread);
milk.Category = beverages;
beverages.Products.Add(milk);
wine.Category = beverages;
beverages.Products.Add(wine);

We do, of course, need to describe this new type (and the relationship between products and categories) in the metadata created withinCreateDSPMetadata:

protected override DSPMetadata CreateDSPMetadata()
{
//Same code to create metadata for Products as before
...
ResourceType category = metadata.AddEntityType(typeof(CategoryEntity),"Category");
metadata.AddKeyProperty(category, "ID");
metadata.AddPrimitiveProperty(category, "Name");
ResourceSet categories = metadata.AddResourceSet("Categories", category);
// Add reference properties between category and product
metadata.AddResourceReferenceProperty(product, "Category", categories);
metadata.AddResourceSetReferenceProperty(category, "Products", products);
return metadata;
}

Note that both ResourceType instances of the related entities need to be at least partially created before you can create and add the navigation properties to them. Also noteProductEntity.Category is a resource reference because there is just one category for a product. On the other hand,CategoryEntity.Products is a resource set reference because it’s a collection.

Now if you follow the code here down into DSPMetadata.cs, you’ll see that theAddResourceReferenceProperty andAddResourceSetReferenceProperty helper functions call into an internal helper functionAddReferenceProperty. (Just to be clear, these are implementation helpers in the sample and not part of any API.) Eventually in that helper you’ll find the creation of the ResourceAssociationSet class, which is provided by the WCF Data Services Framework. This is the object that tells the framework that there's a relationship between two resource sets and which properties contain the links.

Changes in IDataServiceMetadataProvider

As noted earlier, we need to round out the metadata provider implementation to support relationships. This is done through the GetAssociationSet method:

public ResourceAssociationSet GetResourceAssociationSet(ResourceSet resourceSet, ResourceType resourceType,
ResourceProperty resourceProperty)
{
// We have the resource association set precreated on the property annotation,
//so no need to compute anything in here
ResourceAssociationSet resourceAssociationSet = resourceProperty.GetAnnotation().ResourceAssociationSet;
return resourceAssociationSet;
}

As the code comments suggest, much of the work has already been done before this method is ever called, namely in the aforementioned helper functionsAddResourceReferenceProperty andAddResourceSetReferenceProperty that are invoked when the metadata is created. (These are new to the Typed\RWNavProp sample; some long strings have been omitted from this listing.)

// Adds a resource reference property to the specified ResourceType
public void AddResourceReferenceProperty(ResourceType resourceType, string name,ResourceSet targetResourceSet)
{
AddReferenceProperty(resourceType, name, targetResourceSet,false);
}
// Adds a resource set reference property to the specified ResourceType
public void AddResourceSetReferenceProperty(ResourceType resourceType, string name,ResourceSet targetResourceSet)
{
AddReferenceProperty(resourceType, name, targetResourceSet,true);
}
private void AddReferenceProperty(ResourceType resourceType, string name,
ResourceSet targetResourceSet,bool resourceSetReference)
{
PropertyInfo propertyInfo = resourceType.InstanceType.GetProperty(name, BindingFlags.Public |
BindingFlags.Instance);
if (propertyInfo == null)
{
throw new ArgumentException("Can't add a property which does not exist on the instance type.");
}
if (resourceSetReference)
{
if (!typeof(IList).IsAssignableFrom(propertyInfo.PropertyType))
{
throw new ArgumentException(...);
}
Type enumerableOfT =typeof(IEnumerable<>).MakeGenericType(targetResourceSet.ResourceType.InstanceType);
if (!enumerableOfT.IsAssignableFrom(propertyInfo.PropertyType))
{
throw new ArgumentException(...);
}
}
else
{
if (propertyInfo.PropertyType != targetResourceSet.ResourceType.InstanceType)
{
throw new ArgumentException(...);
}
}
ResourceProperty property = newResourceProperty(
name,
resourceSetReference ? ResourcePropertyKind.ResourceSetReference :
ResourcePropertyKind.ResourceReference,
targetResourceSet.ResourceType);
property.CanReflectOnInstanceTypeProperty = true;
resourceType.AddProperty(property);
// We don't support type inheritance so the property can only point to the base resource type
// of the target resource set. We also don't support MEST, that is having two resource sets with
// the same resource type, so we can determine the resource set from the resource type. That also
// means that the property can never point to different resource sets so we can precreate the
// ResourceAssociationSet for this property right here as we have all the information.
property.CustomState = newResourcePropertyAnnotation()
{
InstanceProperty = propertyInfo,
ResourceAssociationSet = newResourceAssociationSet(
resourceType.Name + "_" + name +"_" + targetResourceSet.Name,
new ResourceAssociationSetEnd(resourceType.GetAnnotation().ResourceSet, resourceType, property),
new ResourceAssociationSetEnd(targetResourceSet, targetResourceSet.ResourceType, null))
};
}

With these associations you can now traverse the relationships in the data service URIs, and query across both. For example, you can use a URI likeDemoDSPDataService.svc/Products(0)/Category and see the category of a particular product:

Following a Products-to-Category association in the sample data service.

Figure 6: Following a Products-to-Category association in the sample data service.

Similarly, a URI with Categories(1)/Products will return the products in a given category (in this case the “Beverages” category):

Following a Category-to-Products association in the sample data service.

Figure 7: Following a Category-to-Products association in the sample data service.

Changes in IDataServiceUpdateProvider

Because we now support references, we need to update theDSPUpdateProvider class to implement the AddReferenceToCollection,RemoveReferenceFromCollection, andSetReference methods ofIDataServiceUpdateProvider. As with other update operations, any changes made through these methods also go into the pending changelist (the pendingChanges variable) to be processed when SaveChanges is called.

AddReferenceToCollection

This method is given the handle to the target resource, the name of the reference (navigation) property, and the handle of the resource to add in that property. The short of it here is a simple matter of adding that resource to the collection in the target:

public void AddReferenceToCollection(object targetResource, string propertyName,object resourceToBeAdded)
{
// In our case we make sure that all resource set reference properties are backed by a CLR
// property of type which implements IList so we get the value of the property, cast it to IList
// and add the new resource to the list using that interface. We don't use resource "handles" so
// both resources passed in as parameters are the real resource instances.
//
// Note that we don't support bi-directional relationships so we only handle the one
// resource set reference property in isolation.
// We will use the GetValue we already implement to get the IList
IList list = this.GetValue(targetResource, propertyName) as IList;
if (list == null)
{
throw new ArgumentException("The value of the property '" + propertyName
+ "' does not implement IList, which is a requirement for resource set reference property.");
}
this.pendingChanges.Add(() =>
{
list.Add(resourceToBeAdded);
});
}

RemoveReferenceFromCollection

This method is similarly straightforward, just the inverse of the Add method above:

public void RemoveReferenceFromCollection(object targetResource, string propertyName,object resourceToBeRemoved)
{
IList list = this.GetValue(targetResource, propertyName) as IList;
if (list == null)
{
throw new ArgumentException("The value of the property '" + propertyName
+ "' does not implement IList, which is a requirement for resource set reference property.");
}
this.pendingChanges.Add(() =>
{
list.Remove(resourceToBeRemoved);
});
}

SetReference

Finally, SetReferenceis just a one-line implementation:

public void SetReference(object targetResource,string propertyName, object propertyValue)
{
// Our reference properties are just like normal properties we just set the property value to the new value
// we don't perform any special actions for references. So just call the SetValue which will do exactly that.
this.SetValue(targetResource, propertyName, propertyValue);
}

And with that, we’ve completed a strongly-typed read-write data service with relationship support!

Part 4: Basic Read-Only Untyped Data Provider

We’ll now explore the details of the different untyped provider samples. Each of these solutions contains the same basic structure as the typed samples we’ve been working with: a service project (ODataDemo), a provider project (DataServiceProvider), and a test project (DSPTest). We’ll be starting in this section with the UnTyped\RO sample, followed by UnTyped\RW (Part 5).

As you probably expect, the UnTyped\RO sample is similar to the Typed\RO as they implement the same feature set and, in fact, provide the same data. The big differences are in the underlying classes that compose the data source. Whereas Typed\RO uses specific CLR classes (likeProductEntity), UnTyped\RO simply uses generic classes like Dictionary.

This is clearly seen in the ODataDemo project’s DemoDSPDataService.svc.cs code, specifically within theDemoDSPDataService.CreateDataSource andDemoDSPDataService.CreateMetadata methods. In the former, the data source is initialized using a generalized DSPResource type that’s basically a wrapper for aDictionary containing a bag of properties (see the DataServiceProvider project, DSPResource.cs). Note also that in the UnTyped\RO sample, the data source starts off with bothProducts andCategories and relationships between them, something that wasn’t added in the typed samples until later (see Part 3 above).

protected override DSPMetadata CreateDSPMetadata()
{
DSPMetadata metadata = newDSPMetadata("DemoService","DataServiceProviderDemo");
ResourceType product = metadata.AddEntityType("Product");
metadata.AddKeyProperty(product, "ID",typeof(int));
metadata.AddPrimitiveProperty(product, "Name",typeof(string));
metadata.AddPrimitiveProperty(product, "Description",typeof(string));
metadata.AddPrimitiveProperty(product, "ReleaseDate",typeof(DateTime));
metadata.AddPrimitiveProperty(product, "DiscontinueDate", typeof(DateTime?));
metadata.AddPrimitiveProperty(product, "Rating",typeof(int));
ResourceSet products = metadata.AddResourceSet("Products", product);
ResourceType category = metadata.AddEntityType("Category");
metadata.AddKeyProperty(category, "ID",typeof(int));
metadata.AddPrimitiveProperty(category, "Name",typeof(string));
ResourceSet categories = metadata.AddResourceSet("Categories", category);
// Add reference properties between category and product
metadata.AddResourceReferenceProperty(product, "Category", categories);
metadata.AddResourceSetReferenceProperty(category, "Products", products);
return metadata;
}

If you compare this code to the typed version in Part 1, you’ll see that here we’re explicitly declaring types for every property of the entity types. This wasn’t necessary before with a strongly-typed provider because everything could be determined using reflection.

As a result, the metadata helper class, DSPMetadata (found in DataServiceProvider/DSPMetadata.cs) as it exists in UnTyped\RO now maintains explicit type information for every entity and every property of those entities. (Similarly, theDSPContext class in DataServiceProvider/DSPContext.cs also maintains explicit type information through a class called DSPResource.)

There is one other key difference in theDSPMetadata class implementation, specifically in that properties like ResourceType.CanReflectOnInstanceType andResourceProperty.CanReflectOnInstanceTypeProperty are all set to false:

public ResourceType AddEntityType(string name)
{
ResourceType resourceType = newResourceType(typeof(DSPResource),ResourceTypeKind.EntityType,
null, this.namespaceName, name, false);
resourceType.CanReflectOnInstanceType = false;
resourceType.CustomState = newResourceTypeAnnotation();
this.resourceTypes.Add(resourceType.FullName, resourceType);
return resourceType;
}
publicResourceType AddComplexType(string name)
{
ResourceType resourceType =new ResourceType(typeof(DSPResource),ResourceTypeKind.ComplexType,
null,this.namespaceName, name,false);
resourceType.CanReflectOnInstanceType = false;
this.resourceTypes.Add(resourceType.FullName, resourceType);
return resourceType;
}
public void AddKeyProperty(ResourceType resourceType, string name,Type propertyType)
{
this.AddPrimitiveProperty(resourceType, name, propertyType,true);
}
public void AddPrimitiveProperty(ResourceType resourceType, string name,Type propertyType)
{
this.AddPrimitiveProperty(resourceType, name, propertyType,false);
}
private void AddPrimitiveProperty(ResourceType resourceType, string name,Type propertyType, bool isKey)
{
ResourceType type = ResourceType.GetPrimitiveResourceType(propertyType);
ResourcePropertyKind kind = ResourcePropertyKind.Primitive;
if (isKey)
{
kind |= ResourcePropertyKind.Key;
}
ResourceProperty property = newResourceProperty(name, kind, type);
property.CanReflectOnInstanceTypeProperty = false;
resourceType.AddProperty(property);
}
public void AddComplexProperty(ResourceType resourceType, string name,ResourceType complexType)
{
if (complexType.ResourceTypeKind != ResourceTypeKind.ComplexType)
{
throw new ArgumentException("The specified type for the complex property is not a complex type.");
}
ResourceProperty property = newResourceProperty(name,ResourcePropertyKind.ComplexType, complexType);
property.CanReflectOnInstanceTypeProperty = false;
resourceType.AddProperty(property);
}

Setting these properties to false tell the WCF Data Services Framework that the declaring “type”—in these cases theProduct andCategory types described in the metadata, which are technically just wrappers for Dictionary—don’t directly have properties like “ID” and thus reflection can’t be employed.

To be more specific, when the framework gets a URI like~/Products(2), asking for a specificProducts instance using its ID, the query can’t just try to use an ID property like this:

from p in _queryProvider.GetQueryRootForResourceSet(products)
 where p.ID == 2
 select p;

because ID is not a property of Dictionary. Instead, the query has to look something like this:

from p in _queryProvider.GetQueryRootForResourceSet(products)
 where ((int) DataServiceProviderMethods.GetValue(p,"ID")) == 2
 select p;

which gives your query provider the opportunity to re-write the expression as:

from p in _queryProvider.GetQueryRootForResourceSet(products)
 where ((int) p["ID"]) == 2
 select p;

which is something that LINQ to Objects can handle (or may be easier to translate into SQL).

Note that for all this, the implementation of theIDataServiceMetadataProvider interface doesn’t change; everything here is really just an implementation detail.

Queries in the UnTyped\RO Sample

In addition to the basic changes in the DataServiceProvider project already mentioned above, there’s quite a bit of modified/added code in the untyped version as compare with the Typed\RO sample.

To set the context, let’s again go a little deeper into the whole query mechanism. As noted in Part 1, the WCF Data Services Framework uses LINQ expressions to describe the query it parses from a URI, giving an appropriate expression tree to the data provider to ultimately execute. In the strongly-typed provider case we saw earlier, we were able to get anIQueryable directly from our data source’s CLR classes, which is to say, we took advantage of the built-in LINQ to Objects implementation of IQueryable that works with in-memory object.

In the untyped provider case, your underlying data source can be just about anything, and if you have a LINQ query provider around for that source, the work you have to do in the data provider is to patch up the LINQ expression that the data services framework gives you so it ties into the data source.

Put another way, when the framework parses a URI and creates an expression tree, it knows at different nodes in that expression tree that it needs the value of certain properties. With a strongly-typed provider, it can just use reflection (that is, resource.property) to get the value. With an untyped provider, however, it must ask the provider to retrieve the value of such a property.

How that works involves a class in the framework called DataServiceProviderMethods. This class contains four static methods, GetValue, GetSequenceValue, Convert, and TypeIs, but each one does nothing in itself but throw a “not implemented” exception. “So,” you ask, “what good is that?” Well, by itself, the class isn’t good for anything other than a source of placeholder methods, which is exactly how the framework uses it.

What happens is that at every node in the expression tree where the framework requires a value, sequence value, conversion, or a type comparison, it inserts a method call to one of theDataServiceProviderMethods members. The provider is then expected togo through that expression treeand patch up these methods with calls into the real data source that will return the necessary information (see Figure 8). Only then can the expression tree be passed to an appropriate query provider and executed; otherwise exceptions will occur.

Walking an expression tree to patch up DataServiceProviderMethods

Figure 8: A custom untyped data provider must walk the LINQ expression tree generated by the WCF Data Services Framework and replace any calls to members of DataServiceProviderMethodswith calls to real methods in the data source.

The code that does all this in the UnTyped\RO sample involves a number of distinct classes. The first one to look at is calledDSPMethodTranslatingVisitor, which, as its name suggests, is an agent that does some method patch-ups in an expression tree. What it is doing follows a general implementation pattern in LINQ that’s fully described in the How to: Modify Expression Trees topic on MSDN. The basic idea is that you walk the tree using what’s called an Expression Visitor,the code for which is given in the How to: Implement an Expression Tree Visitor topic and found in the UnTyped\RO solution in DataServiceProvider/ExpressionVisitor.cs. (Note: theExpressionVisitor class has become a standard part of the .NET Framework version 4; a private implementation is only necessary for data services that target .NET Framework 3.5.)

Our DSPMethodTranslatingVisitor, then, as you can see in the C# file by the same name, is a class derived from ExpressionVisitor whose sole purpose is to find and patch up calls to members of DataServiceProviderMethods.

internal class DSPMethodTranslatingVisitor : ExpressionVisitor
{
internal static readonly MethodInfo GetValueMethodInfo =typeof(DataServiceProviderMethods).GetMethod(
"GetValue",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(object), typeof(ResourceProperty) },
null);
internal static readonly MethodInfo GetSequenceValueMethodInfo =typeof(DataServiceProviderMethods).GetMethod(
"GetSequenceValue",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(object), typeof(ResourceProperty) },
null);
internal static readonly MethodInfo ConvertMethodInfo =typeof(DataServiceProviderMethods).GetMethod(
"Convert",
BindingFlags.Static | BindingFlags.Public);
internal static readonly MethodInfo TypeIsMethodInfo =typeof(DataServiceProviderMethods).GetMethod(
"TypeIs",
BindingFlags.Static | BindingFlags.Public);
public static Expression TranslateExpression(Expression expression)
{
DSPMethodTranslatingVisitor visitor = new DSPMethodTranslatingVisitor();
return visitor.Visit(expression);
}
internal override Expression VisitMethodCall(MethodCallExpression m)
{
if (m.Method == GetValueMethodInfo)
{
// Arguments[0] - the resource to get property value of - we assume it's a DSPEntity
// Arguments[1] - the ResourceProperty to get value of
// Just call the targetResource.GetValue(resourceProperty.Name)
return Expression.Call(
this.Visit(m.Arguments[0]),
typeof(DSPResource).GetMethod("GetValue"),
Expression.Property(m.Arguments[1],"Name"));
}
else if (m.Method.IsGenericMethod && m.Method.GetGenericMethodDefinition() == GetSequenceValueMethodInfo)
{
// Arguments[0] - the resource to get property value of - we assume it's a DSPEntity
// Arguments[1] - the ResourceProperty to get value of
// Just call the targetResource.GetValue(resourceProperty.Name) and cast it to the right
// IEnumerable<T> (which is the return type of the GetSequenceMethod
return Expression.Convert(
Expression.Call(
this.Visit(m.Arguments[0]),
typeof(DSPResource).GetMethod("GetValue"),
Expression.Property(m.Arguments[1],"Name")),
m.Method.ReturnType);
}
else if (m.Method == ConvertMethodInfo)
{
// All our resources are of the same underlying CLR type, so no need for conversion of the CLR type
// and we don't have any specific action to take to convert the Resource Types either (as we
// access properties from a property bag). So get rid of the conversions as we don't need them
return this.Visit(m.Arguments[0]);
}
else if (m.Method == TypeIsMethodInfo)
{
// Arguments[0] - the resource to determine the type of - we assume it's a DSPEntity
// Arguments[1] - the ResourceType to test for
// We don't support type inheritance yet, so simple comparison is enough
return Expression.Equal(Expression.Property(this.Visit(m.Arguments[0]),
typeof(DSPResource).GetProperty("ResourceType")), m.Arguments[1]);
}
return base.VisitMethodCall(m);
}
}

All that’s going on here is that this class cachesMethodInfo for each of the fourDataServiceProviderMethods we care about. Then it overrides ExpressionVisitor.VisitMethodCall, which is called every time a method call node is encountered when walking the expression tree. In response, we check if the method call in question matches one of theDataServiceProviderMethods. If so, we then replace that method call (which again, just throws a not-implemented exception) with one in our own data source (DSPResource).

So now that we have the machine in place to do the patch-up, let’s see how it’s called upon to do the job. That happens through this chain of events:

1. The WCF Data Services Framework calls the implementation of IDataServiceQueryProvider.GetQueryRootForResourceSet which exists in our DSPResourceQueryProvider class.

2. That method calls DSPLinqQueryProvider.CreateQuery to generate the appropriate IQueryable instance.

public System.Linq.IQueryable GetQueryRootForResourceSet(ResourceSet resourceSet)
{
return DSPLinqQueryProvider.CreateQuery(this.dataSource.GetResourceSetEntities(resourceSet.Name).AsQueryable());
}

3. The DSPLinqQueryProvider class is, in our implementation, just a wrapper for the LINQ to Objects query provider (the standard .NET implementation ofIQueryable), stored internally as the variable underlyingQueryProvider. Its static CreateQuery method (invoked above), simply asks LINQ to Objects for an expression and then wraps it in out own query class.

internal class DSPLinqQueryProvider : IQueryProvider
{
private IQueryProvider underlyingQueryProvider;
...
public static IQueryable CreateQuery(IQueryable underlyingQuery)
{
DSPLinqQueryProviderprovider = newDSPLinqQueryProvider(underlyingQuery.Provider);
return provider.CreateQuery(underlyingQuery.Expression);
}
...
}

4. As is true with LINQ in general, a query isn’t executed until its results are actually enumerated. In the meantime, the WCF Data Services Framework will be calling other methods in the query provider to build the expression tree (nodes of which use our internal classDSPLinqQuery); see theIQueryProvider.CreateQuery<TElement> and IQueryProvider.CreateQuery methods in DSPLinqQueryProvider.cs.

5. When the query is finally executed, calls are generated to IQueryable.GetEnumerator. In cases where a single result is returned, a call is generated to either IQueryProvider.Execute<TResult> or IQueryProvider.Execute, both of which are again in DSPLinqQueryProvider.cs:

public TResult Execute<TResult>(Expression expression)
{
expression = this.ProcessExpression(expression);
return this.underlyingQueryProvider.Execute<TResult>(expression);
}
public object Execute(Expression expression)
{
expression = this.ProcessExpression(expression);
return this.underlyingQueryProvider.Execute(expression);
}

6. Both of these invoke the private methodDSPLinqQueryProvider.ProcessExpression, which in turn calls the static method DSPMethodTranslatingVisitor.ProcessExpression:

private Expression ProcessExpression(Expression expression)
{
return DSPMethodTranslatingVisitor.TranslateExpression(expression);
}

7. This method, then, creates an instance ofDSPMethodTranslatingVisitor and calls its Visit method to walk the tree, thereby patching up all the necessary method calls:

public static Expression TranslateExpression(Expression expression)
{
DSPMethodTranslatingVisitorvisitor = newDSPMethodTranslatingVisitor();
return visitor.Visit(expression);
}

8. Finally, the patched-up expression tree comes back to one of the Execute methods in step 5 above, which is then passed on to LINQ to Objects and executed against our in-memory data source.

Obviously, when you’re dealing with a data source that isn’t in memory, you’ll need to invoke a different LINQ query provider to do the deed if, in fact, you more or less follow the structure of this sample. But as you can see, the whole process is one of again patching up a LINQ expression tree with the appropriate methods into the data source, then executing that expression tree. You can, of course, execute that query in any number of ways that don’t involve LINQ at all, such as translating into SQL statements.

The bottom line is that LINQ is only being used here as the language for describing a query, and that’s where the required understanding and usage of LINQ ends. The real point is running queries against your data source, and when you can do that, you have a fully-operational read-only provider in hand.

Part 5: Adding Insert, Update, and Delete Support to an Untyped Provider

As with the delta between the Typed\RO and Typed\RW samples, the difference between UnTyped\RO and UnTyped\RW is the addition of anIDataServiceUpdateProvider implementation, which is found in the latter’s DataServiceProvider/DSPUpdateProvider.cs file. Here the requirements and operation of this interface, especially in maintaining a change list that’s not applied until IDataServiceUpdateProvider.SaveChanges is called, are completely identical since the contract of the interface is the same regardless of the data source. Again, the WCF Data Services Framework doesn’t care about the internal details of the provider, only about it fulfilling its part of the contract.

With that in mind, there’s little more to say about the UnTyped\RW sample as the only real differences from Typed\RW have to do with the underlying type system. As noted already, the strongly-typed provider sample uses distinct CLR classes in its backing store, whereas the untyped sample uses its generic DSPResource/i> wrapper class. But in the end, these are just implementation details. The necessary structural understanding on the provider level is the same as what was already covered in Part 2.

Going Further

In this document we’ve explored the basic structure of a custom data service provider with both read-only and read-write variants and working with both typed and untyped data sources. We’ve also explored what it means to support relationships/associations/navigation properties.

Custom providers can also implement additional interfaces to support other features. For details on these and other matters, see the following links:

Microsoft is conducting an online survey to understand your opinion of the MSDN Web site. If you choose to participate, the online survey will be presented to you when you leave the MSDN Web site.

Would you like to participate?