Generated code - WCF Data Services support

LLBLGen Pro Runtime Framework supports the WCF Data Services shipped by Microsoft. The support is for .NET 4 and higher. LLBLGen Pro Runtime Framework comes with a full Data Service Provider implementation which makes it real easy to create your own WCF Data services based on a model in Adapter or SelfServicing code.

The WCF Data Services is designed to be usable with existing Visual Studio.NET tooling. The following information describes how to define a WCF Data Services for generated code in Adapter or SelfServicing. The example below will add a service for 'Northwind', using Adapter, however you can use the same code for SelfServicing.

LLBLGen Pro WCF Data Services support is implemented in the SD.LLBLGen.Pro.ODataSupportClasses.dll assembly. This assembly supports OData v1, v2 and v3 and is compiled against v5.7.0 of the WCF Data Services Server assembly available from nuget, instead of the .NET 4 version of the same assembly.

Your application will therefore need to reference this same assembly (or newer) to use ODataSupportClasses. This nuget based WCF Data Services Server assembly supports OData v1, 2 and 3.

If you need OData v4 support and thus want to have the OData Support Classes compiled against v6.x of the WCF Data Services, you have to compile the OData Support Classes yourself from the source code archive of the LLBLGen Pro Runtime Framework, which is available to you on the LLBLGen Pro website, in the customer area under 'Extras'. v6.x isn't backwards compatible with OData v3 and earlier hence this limitation.

Info

By default, the LLBLGen Pro runtime framework setting 'ConvertNulledReferenceTypesToDefaultValue' is set to true. This results in null string fields to return the value "" instead of null. As a result, the string field will be set to "" when updating the entity instead of null and this could lead to wrong updates for these fields as "" might mean something else in your application than null. To overcome this, set 'ConvertNulledReferenceTypesToDefaultValue' to false in the project settings (in the Conventions: LLBLGen Pro Runtime Framework node)

Creating a WCF Data Services Tutorial

  • The VS.NET project (using .NET 4.0 or higher) which will host the WCF Data Services service has to have a reference to the following LLBLGen Pro runtime framework dlls:
    • SD.LLBLGen.Pro.ORMSupportClasses.dll
    • SD.LLBLGen.Pro.ODataSupportClasses.dll
  • The VS.NET project also has to have references to the generated code projects. For Adapter, you need to reference both projects.
  • Code has to be generated for .NET 4.0 or higher
  • The VS.NET project has to have a reference to the WCF Data Services Server assembly available from nuget
  • To add a WCF Data Services service class, right-click the VS.NET project to which to add the service to in Solution Explorer and select 'Add -> New item…'
  • Select under 'Web' (be sure to select .NET 4 or higher) the item WCF Data Service and specify a proper name for the file/service and click Add.
  • This will add a yourservicename.svc file with associated yourservicename.svc.cs/vb file. Open the service's code by right-clicking it in Solution Explorer and selecting 'View code'.
  • The service class derives by default from i. This has to be changed to LLBLGenProODataServiceBase<LinqMetaData>. The LLBLGenProODataServiceBase is in the namespace SD.LLBLGen.Pro.ODataSupportClasses (so you have to add a using/imports statement for that namespace at the top of the file, use the smart-tag in the VS.NET editor for that). The LinqMetaData class is in the generated code of the model you want to expose through the service. This class is in the namespace Rootnamespace.Linq, where Rootnamespace is the namespace you specified when you generated code in the designer.
  • The service, which we've called NorthwindService, now looks something like this: (in red the changes are specified you have to make.) Note that RootNamespace is the root namespace of your generated code.

    using System;
    using System.Collections.Generic;
    using System.Data.Services;
    using System.Data.Services.Common;
    using System.Linq;
    using System.ServiceModel.Web;
    using System.Web;
    using SD.LLBLGen.Pro.ODataSupportClasses;
    using RootNamespace.Linq;
    
    namespace MyNamespace
    {
        public class NorthwindService : LLBLGenProODataServiceBase<LinqMetaData>
        {
            // This method is called only once to initialize service-wide policies.
            public static void InitializeService(DataServiceConfiguration config)
            {
                // TODO: set rules to indicate which entity sets and service operations 
                // are visible, updatable, etc.
                // Examples:
                // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
                // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
                config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
            }
        }
    }
    
  • For development it's key to add the following attribute to the service class, so exceptions are returned in full to the client. It's recommended this attribute is removed when the service is in production:
    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    
  • In the InitializeService method, you can add additional configuration options. For example to be able to fetch all entities, add:
    config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
    
    It's also recommended to add config.UseVerboseErrors=true; to this method during development.
  • There are three methods in the service class which have to get an implementation. They're specified below in code and are simply factory methods. In comments, selfservicing variants are given.

    protected override LinqMetaData CreateLinqMetaDataInstance()
    {
        // adapter
        return new LinqMetaData(new DataAccessAdapter());
        // selfservicing:
        // return new LinqMetaData();
    }
    
    protected override ITransactionController CreateTransactionControllerInstance()
    {
        // adapter
        return new DataAccessAdapter();
        // selfservicing:
        //return new Transaction(System.Data.IsolationLevel.ReadCommitted, "Trans");
    }
    
    protected override IUnitOfWorkCore CreateUnitOfWorkInstance()
    {
        // adapter
        return new UnitOfWork2();
        // selfservicing
        // return new UnitOfWork();
    }
    
  • You have to specify an override to the property ContainerName, which should return the name of the service. In our example this becomes:
    protected override string ContainerName
    {
        get { return "NorthwindService"; }
    }
    
  • Optional: you can specify an override to the property ContainerNamespace. By default it uses the namespace of the LinqMetaData type, but it's recommended to specify a namespace which makes sense in the context of the service, as this namespace is used for the entities returned from the service, e.g. you can use the namespace or name of the project containing the service.
  • Optional: If you're using ORM Profiler, add the call to the interceptor to the InitializeService() method.
  • The VS.NET project containing the service needs to be able to read the connection string. It's therefore key to add the connection string for the generated code to the project's app.config or web.config file.
  • The service is now ready to use. To the service file you can add WebGet methods and other methods, e.g. methods annotated with QueryInterceptor attributes.
  • To test the service, compile and start it. If you have added the service to a Website application, you can simply start the service by requesting it from a web browser. To see whether the meta-data is fully obtained, append $metadata to the service URL.
  • See http://www.odata.org for details about using WCF Data Services.

The easiest way to consume the service in a .NET application is to add a Service Reference (in VS.NET) of the service you just created. This way VS.NET will generate classes for you, one for each entity exposed by the service and a context class to work with the entity classes on the client.

Limitations

We did our best to support WCF Data Services in full to make sure everything in your LLBLGen Pro model/generated code is exposable through the service. However as it turned out, WCF Data Services in its current version (.NET 4 framework) has limitations and these limitations can make it a problem to expose the generated code through a service, even though the generated code works OK when used in normal .NET code. These limitations are described below.

Most of them will result in an error either when you obtain the $metadata or when you fetch data. If you run into one of these limitations, it's best to create a copy of the LLBLGen Pro project file and remove the entities/fields/types which cause a problem and re-generate the code, and use that in the service.

No enum / DateTime2 etc. support

As WCF Data Services builds an entity data model (EDM), the types are based on the EDM type system. This means that it can't deal with fields which have an Enum type. The LLBLGenProODataServiceBase class filters out fields with Enum types and all other types which aren't mappable to an EDM type.

These fields don't show up in the entity exposed by the service. The types which are supported are listed in the list of DataTypes on this page: http://msdn.microsoft.com/en-us/library/dd723653.aspx. Our provider doesn't use the Reflection provider, but the same list of types applies.

No byte[] type support for PK fields.

A field in the primary key of an entity isn't allowed to be of type byte[]. This is a limitation of WCF Data Services, and a left-over from Entity Framework, which has the same limitation.

Typed Views and OData

When generating typed views as poco classes with linq, they can be used with OData, using WCF Data Services and the OData Support Classes. However due to limitations in WCF Data Services from Microsoft, the typed views are not directly queryable, as they are defined as complex types in the OData service, not as entity types.

The main reason for that is that entity types require a PK in WCF Data Services and typed views don't have a PK. To use the typed views in OData a service method has to be added which returns an IQueryable and which has a [WebGet] attribute.

It's still not possible to use orderby, paging and the like on this method due to limitations in WCF Data Services with respect to complex types, unfortunately.

The WCF Data Services client is even more limited in that it can't deal with a collection of complex types, so the service method's return type won't be consumable from a .net class if the client is used: the more low-level reader has to be used in this case.

No navigators supported in subtypes if you select OData v1 or OData v2

If you select OData v1 or OData v2 using the DataServiceBehavior settings, navigators on subtypes are filtered out by the ODataSupportClasses. This is a limitation of WCF Data Services in .NET 4 / 4.5. This limitation means that if you have inheritance in your entity model, any subtype can't have a relationship with another entity, all relationships have to be defined on the root of the hierarchy.

This is quite a limiting factor for many people. If you run into this, be sure to set the OData protocol to v3 using the following in the InitializeService method:

config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;

Many to Many relationships are single sided and read-only

In LLBLGen Pro, many to many relationships are always read-only and this won't change in a WCF Data Service. To update many-to-many relationships, you have to append entities over the intermediate entity and the 1:n/m:1 relationships instead, like you'd do normally when using LLBLGen Pro code.

$Expand has some limitations with fetches 2+ levels deep

The WCF Data Services framework creates a projection of nested queries to implement an $Expand directive. This in general works however it goes wrong with expanded m:1/1:1 relationship navigators which are 2+ levels deep, due to a limitation in our Linq provider.

Our Linq provider fetches nested queries using separate, prefetch path like queries which are merged in memory and which utilize optimizations based on parent sets. The downside is that to be able to do this, the nested query has to be configured in such a way that there's a correlation filter constructable to tie nested query to outer query.

The way WCF Data Services constructs the nested queries makes this currently not possible to do, so these nested queries fail.

Acknowledgements

We'd like to thank Brian Chance for his work in this area. His templates (which used the RelfectionProvider) to bring WCF Data Services support to LLBLGen Pro were a great source of information during the development of the data source provider.