In my last project I had the assignment to create a client server application. Not that hard. We chose to use WCF for the communication itself, because that was easy, straightforward and well supported with .NET 3.5SP1 (and Visual Studio 2008 of course).
As we didn’t have the time to create our own mapper to map the database to code, nor did we have time to study another mapper (nHibernate came to the question) we choose for something we already knew. LINQ 2 SQL.
Now we all used it in a connected environment. But as you all know, WCF is not connected. The DataContext is lost upon transmitting the entity to the client.
Now that’s the problem, change tracking is lost and LINQ 2 SQL doesn’t play well with reattaching detached entities.
Transmitting an entity from server to the client was no problem, with the DataContext.LoadOptions we were able to eager load all the entities required and transmit them to the client. And we didn’t want to use a third party template or baseclass because those weren’t approved by management 🙂 .
In order to make LINQ to SQL add DataMember and DataContract properties to your classes you need to set the Serialization Mode to Unidirectional (as shown below):
In code you can now see attributes like this:
But the DataMemberAttribute is not applied on EntityRef, only EntitySet.
EntityRef is used to lazy-load a reference to 1 entity. EntitySet is used for a list of lazy-loadable entities.
Let me explain with an example (I’m using the Northwind database).
A Product has 1 category and a Category has multiple Products.
This means that a Category has an EntitySet<Product> (as shown below, notice the DataMemberAttribute):
While a Product has only 1 Category (notice the absence of the DataMemberAttribute):
When working in a connected environment the absence of those attributes is not a problem. But on WCF it is. Since you cannot do lazy loading when the entity is transmitted to the client you need to make sure you load everything in front.
While you can create DataLoadOptions for a Product.Category link the absence of the DataMemberAttribute prevents the WCF serializer from serializing the Category Entity.
- using (NorthwindDataContext northwindDataContext = new NorthwindDataContext())
- var dataLoadOptions = new DataLoadOptions();
- dataLoadOptions.LoadWith<Product>(p => p.Category);
- northwindDataContext.LoadOptions = dataLoadOptions;
- return northwindDataContext.Products.Where(p => p.ProductID == 1).SingleOrDefault();
When invoking this method on the client you will see that the received Product’s Category is null.
First of all: why is there no DataMemberAttribute on the EntityRef in LINQ2SQL?
Imagine me having a Category, and eager (opposite of lazy) loading all the Product entities. All those products have 1 category, and they if I set the Product.Category to eager load… think about it, this will cause an infinite loop:
Category –> x Products, for each of those products load their category, for each of those categories, load their products and so on.
Boom, the WCF XML serializer cannot handle this.
How do we solve this?
In the LINQ2SQL designer take the Product.Category (or Category.Products if you will) link, and set the Child Property to false.
This will remove the Category.Products property and generate a DataMemberAttribute on the Product.Category. Of course now you cannot get all the products in a category through a Category link this:
But you have to do something like this:
This generates the same SQL statement on the backend and now you can properly sent those entities to your client.