Hi,
I'm looking for a way to expose a subset of my existing business layer (which is based on LinqToSql) via WCF Data Services so that
The Data Service can still be consumed (read AND write) by older odata clients as my business layer evolves over time. It seems that the only way to guarantee this is by making sure the data service does not change, even though the business layer changes.
Some properties of my business objects are hidden.
The data service can handle relatively large databases (tens of thousands of business entities)
- It doesn't take me several weeks to implement the data service
I've tried a few different approaches, but each of them has considerable downsides. Any guidance on things I have overlooked would be greatly appreciated.
These are the approaches I have tried so far:
First Approach: Reflection based provider implementing IUpdateable, business objects implementing a well-defined interface
Instead of exposing my LinqToSQL objects directly, I tried having them implement an interface, which defines the properties I want to expose and expose only that interface on the data service. For example If I have a Customer linq entity, I have it implement an ICustomer interface:
public interface ICustomer{
int ID{get;set;}
string Name{get;set;}
}
//My Linq entity
public partial class Customer: ICustomer{
public int ID{get;set;}
public string Name{get;set;}
public string SomeOtherPropertyIDoNotWantToExpose{get;set;}
}
Then in my Data Service Provider I simply put a property like
public IQueryable<ICustomer> Customers {
get {
//Note that the method below returns an IQueryable<Customer>
return MyBusinessLayerCustomersProvider.LoadAllCustomers();
}
}
Unfortunately this does not work. When trying to access the data service from the client side and use any Linq Method relying on OrderBy (such as myContext.Customers.First() ), I get the following error:
No generic method 'OrderBy' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
I'm not sure why this happens, but I'm stuck at this point.
Second Approach: Reflection based provider implementing IUpdateable, wrapper class around my exising entities
In this case I tried implementing classes around my linq entities and exposing the wrapper class with the data service. Something like:
//My Linq entity
public partial class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public string SomeOtherPropertyIDoNotWantToExpose { get; set; }
}
//the wrapper
public partial class WrappedCustomer
{
internal Customer _wrappedEntity;
public int ID { get{ return _wrappedEntity.ID;} set{ _wrappedEntity.ID = value;} }
public string Name { get { return _wrappedEntity.Name; } set { _wrappedEntity.Name = value; } }
}
The problem with this approach is that it does not work unless I load all Customer entities from my database to memory with every request to the data service. That's because the Customers property on the on the Data Service provider looks like:
public IQueryable<Customer> WrappedCustomer
{
get
{
IQueryable<WrappedCustomer> customers = from c in MyBusinessLayerCustomersProvider.LoadAllCustomers
select new WrappedCustomer{_wrappedEntity = c};
//I am casting to list to avoid "no supported translation to sql" errors.
return tasks.ToList().AsQueryable();
}
}
So even if the page size for my wcf data service is limited to 100, the code above would load ALL customer entities to memory before it can find the proper entities that the client actually wants to load. So this does not seem to scale properly.
Third Approach: Expose LinqToSQL entities directly, use IgnoreProperties attribute
In this case I would have my data service return the linqtosql entities originating from my business layer directly and use the IgnoreProperties attribute to hide the properties I do not want to expose in the data layer. This approach scales and is easy to implement, but it does not support versioning of the data service properly. For example if I want to implement a version 2 of my data service, I might need to hide less linq entity properties than in the first service. and there doesn't seem to be a way to use several IgnoreProperties attributes on a single LinqToSQL entities.
Fourth approach: Implement a custom data service provider
It seems that custom data service providers can easily cover all of my requirements. Now it might be just me, but this looks too complicated. It seems I have to implement IDataServiceMetadataProvider, IDataServiceQueryProvider, IDataServiceUpdateProvider, and IServiceProvider, none of which are trivial. And even after reading Alex J's article series on this subject and having a look at the sample provided on the odata website, I still don't get what most of the functions I need to implement actually do.
It seems much more complicated that just implementing IUpdateable, especially since I do not know which of the code portions provided in the samples can simply be copied and pasted or need to be customized.
Fifth approach: Use EF and find some way to make the business layer work with both Linq to SQL and EF
I succeeded in implementing a proof of concept for this, but it's rather messy, so I'd only use this as a last resort.
So, what have I overlooked? What should I do next? I'm running out of time, so any advice would be greatly appreciated.
Thanks,
Adrian