Returning LINQ to SQL Entities from a WCF Service

Monday, 6 October 2008 10:50 by slanford

When returning LINQ to SQL entities from a WCF service using the default properties for your LINQ to SQL entity model you will almost certainly encounter an immediate error when you attempt to call a service method. In fact, the only scenario I can think of in which you may not receive out-of-the box errors is if your model contains no relationships. For example, consider the following entity model and service method.

NOTE: The following scenario will only work if running service pack 1 for the .NET Framework 3.5 because the LINQ to SQL entity model is set to the default serialization mode of "None". This will work with SP1 because SP1 adds implicit DataContractSerializer support for Plain Old CLR Objects (POCO) by serializing public fields and read/write properties. Later in this post you'll see how to change the serialization mode to remove this restriction as well as solve the issue being presented.

LINQ to SQL Entity Model  (with Serialization Mode = "None")

        public List<Data.Employee> GetEmployees()
        {
            Data.NorthwindDataContext context = new Data.NorthwindDataContext();
 
            List<Data.Employee> employees = (  from e in context.Employees
                                             select e).ToList<Data.Employee>();
 
            return employees;
        }

WCF Service Method 

Now at this point if I call GetEmployees() from a client I would be greeted with a "The underlying connection was closed: The connection was closed unexpectedly." WebException. Why is this? The obvious answer is some exception occurred in the service method...but what? My first instinct was that it had something to do with the deferred loading mechanism of LINQ to SQL since the above scenario works without error if I simply remove the entity model relationship. Regardless of the cause I knew that the error must be happening during serialization since I could step through the service method without error. So as a quick way to view the underlying exception without having to enable tracing on the server I modified the service method to serialize my result inline.

        public List<Data.Employee> GetEmployees()
        {
            Data.NorthwindDataContext context = new Data.NorthwindDataContext();
 
            List<Data.Employee> employees = (  from e in context.Employees
                                             select e).ToList<Data.Employee>();
 
            //
            //Serialize the result to debug error
            //
            try
            {
                DataContractSerializer dcs = new DataContractSerializer(typeof(List<Data.Employee>));
                using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
                {
                    dcs.WriteObject(ms, employees);
                }
            }
            catch (Exception ex)
            {
 
            }
 
            return employees;
        }

Modifed Debug Version of WCF Service Method 

Now when stepping through the service method I was able to see the actual exception which was not what I initally expected. The exception was "Object graph for type ... contains cycles and cannot be serialized if reference tracking is disabled." The cause of this error is due to circular references in the entity objects as a result of the defined relationship; Employee objects have a reference to one or more EmployeeTerritory objects and each EmployeeTerritory object has a reference back to its parent Employee object which can't be represented when serialized unless we enable the preservation of theses object references during serialization. There are two ways to enable object reference preservation for our LINQ to SQL classes.

1. The first way is to instruct the DataContractSerializer to preserve object references by setting the preserveObjectReferences of the appropriate constructor overload to true. For details on how to do this in WCF see Sowmy Srinivasan's post here. Note that even though this seems to work in my limited testing it is not supported. To quote the MSDN documentation here..."Unidirectional serialization is the only type of serialization supported by LINQ to SQL."

2. That brings us to the second and recommended way of enabling object reference preservation; change the Serialization Mode for the entity model to "Unidirectional" in Visual Studio.

With unidirectional serialization, as the name implies, bidirectional associations are not maintained. That is, the serialized object will only maintain a one-way association to avoid a circular reference. By convention, the property on the parent side of the association is serialized and the property on the child side of the association is not. So in my example the Employee.EmployeeTerritories[] property would be serialized but the EmployeeTerritory.Employee property would not.

There is however an unintended side-effect of changing the Serialization Mode. That is related data is not automatically fetched as the object graph is walked upon serialization. So in this example if we simply change the serialization mode to "Unidirectional" and do nothing else, what we get on the client side is a list of Employee objects with a null EmployeeTerritories[] property. Fortunately there is a relatively easy way to solve this by instructing LINQ to SQL to retrieve the EmployeeTerritory data along with the Employee data. To do this we need to specify the DataLoadOptions for the DataContext. Here is the modified WCF service method.

 
        public List<Data.Employee> GetEmployees()
        {
            Data.NorthwindDataContext context = new Data.NorthwindDataContext();
            System.Data.Linq.DataLoadOptions dlo = new System.Data.Linq.DataLoadOptions();
            dlo.LoadWith<Data.Employee>(e => e.EmployeeTerritories);
            context.LoadOptions = dlo;
 
            List<Data.Employee> employees = (  from e in context.Employees
                                             select e).ToList<Data.Employee>();
 
            return employees;
        }
Modified WCF Service Method To Include EmployeeTerritory Data In Results 

Now when we call this service method we get both Employee and EmployeeTerritory data at the client as expected.

Categories:   LINQ to SQL | WCF
Actions:   E-mail | Permalink | Comments (5) | Comment RSSRSS comment feed