LINQ to SQL Serialization bites me again
I'm mucking around with a bit of code today that's using AJAX and returning some data from my business layer that's using LINQ to SQL Entities. I mentioned some time that one issue that I ran into and continue to bump my head against is that LINQ to SQL has big issues serializing its entities. The problem with serialization of any kind is that L2S creates cirular references with default models and these circular references are blowing up serialization.
As I mentioned in my last post this causes serialization problems using any serialization format although it's possible to work around this by setting up the relationships in a certain way so that there's no circular referencing going on.
The problem is that this is not realistic in most applications and you do in fact want this circular referencing to occur with lazy loaded entities.
So today I'm plugging away at some code that's using some AJAX remote callbacks that use my own wwMethodCallback Ajax control and it of course has the same issues as the .NET native serializers. It also chokes on the circular referencing.
In my scenario I wanted to do something as simple as this:
[CallbackMethod]public object UpdateTotals(string Pk)
{ int pk = 0;int.TryParse(Pk, out pk);
Entry.Load(pk);
this.UpdateTotalsOnEntry();
return Entry.Entity;}
This fails because the Entry entity references a Project Entity for a 1 - 1 relation ship and the Project entity in turn points back to the in a 1 to many to the entries. In my previous post I mention some ways to get around this with Xml and WCF Serialization but it doesn't help me here with my own serializer or the MS AJAX JSON serializer which don't look at the DataContract attributes (I guess I'll fix that in mine <s>).
When I run this code the app churns for a bit and then finally fails with Stack Overflow.
Besides the solution of setting up the object hierarchy to completely avoid nested references there are a couple of other ways to handle this.
The first is kind of brute force by explicitly going in and removing setting all the relationship properties to null:
[CallbackMethod]public object UpdateTotals(string Pk)
{ int pk = 0;int.TryParse(Pk, out pk);
Entry.Load(pk);
this.UpdateTotalsOnEntry(); Entry.Entity.Project = null; Entry.Entity.User = null; Entry.Entity.Invoice = null; return Entry.Entity;}
This actually works and results in just the actual entity to be serialized down to the client, which in the scenario above actually is exactly what I need. I got to thinking though, it would be nice to try out and also serialize the project so I tried the following code:
// *** Load Entity into Entry.EntityEntry.Load(pk);
this.UpdateTotalsOnEntry();// *** Access on project again: Fails. Project is nulldd = Entry.Entity.Project.Entered;
Entry.Entity.User = null;Entry.Entity.Invoice = null;// *** Kill the circular referenceEntry.Entity.Project.Entries = null;
return Entry.Entity;The intent in that code is to allow project to serialize, but cut off the circular reference. Unfortunately this didn't work correctly - for some reason when I set Entry.Entity.Project.Entries to null Entry.Entity.Project also gets set to null resulting in the Project not getting serialized either. Not sure if this a bug or by design - I suppose it could be by design that if you kill a required relationship the parent object disappears?
IAC the above is probably not a good way to do this anyway as it requires way too much inside knowledge for the front end layer.
In the end the better solution was actually creating a new anonymous type and return that instead with just the data that the client actually requires. Anonymous types are great that way: The make it extremely easy to create light weight message objects for passing back to the client. In the code above I used this code which returned only a couple of values that the client actually requires:
[CallbackMethod]public object UpdateTotals(string Pk)
{ int pk = 0;int.TryParse(Pk, out pk);
// *** Load Entity into Entry.EntityEntry.Load(pk);
this.UpdateTotalsOnEntry();return new { TotalHours = (decimal) Entry.Entity.TotalHours,
ItemTotal = (decimal) Entry.Entity.ItemTotal };}
This sidesteps the whole serialization issue and also reduces the amount of data on the wire significantly. Instead of the full entity going back to the client now only the two data items go back.
Other Posts you might also like
- Map Physical Paths with an HttpContext.MapPath() Extension Method in ASP.NET
- Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application
- Getting the Client IP Address in ASP.NET Core
- Resolving Paths To Server Relative Paths in .NET Code
- Getting the ASP.NET Core Server Hosting Urls at Startup and in Requests
The Voices of Reason
# re: LINQ to SQL Serialization bites me again
http://www.codeproject.com/KB/dotnet/linqsqlserialization.aspx
# re: LINQ to SQL Serialization bites me again
http://www.codeproject.com/KB/dotnet/linqsqlserialization.aspx
In the specified example Product / Supplier. Why would you want to add a Products property in the Supplier table. This has really confused me. I just do not understand what Linq is trying to model with this relationship. What is wrong with a Data Method GetProductBySupplier(Supplier supplier) in the Product Class. To me Linq does not appearto be doing that DAL function of mapping DB Objects(results) to Application Objects(based on the real world).
24 hours of looking at linq and I am slowly starting to think I was right about its inability to correctly abstract the database from BLL, to use this correctly I would need to add aditional mapping layers. There may be some merit in creating the mapping classes manually but then where is the time benefit in this. It would not even be that simple to create a proof of concept using Linq and then convert to using a standard DAL the work involved in re-modeling the class would be extreme
24 Hours and
- SOAP compatibility/ serialization problems
- Inheritance problems cannot use inherited classes i.e. ModelBase class
Time to park it for another day. Dissapointing!!!
# re: LINQ to SQL Serialization bites me again
you should just be able to set the serialization to unidirectional.
# re: LINQ to SQL Serialization bites me again
I'm really not sure if it helps, but Scott Guthrie today made an interesting post in his Blog:
http://weblogs.asp.net/scottgu/archive/2007/10/01/tip-trick-building-a-tojson-extension-method-using-net-3-5.aspx
He uses the System.Web.Script.Serialization.JavaScriptSerializer in an extension method to serialize any object to a JSON string. This would not been too interesting yet, but there is a property called RecursionLimit that may help:
As I mentioned, I didn't try it myself yet, it just seemed to fit here.
HTH, Dennis