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.Entity
Entry.Load(pk);
this.UpdateTotalsOnEntry();
// *** Access on project again: Fails. Project is null
dd = Entry.Entity.Project.Entered;
Entry.Entity.User = null;
Entry.Entity.Invoice = null;
// *** Kill the circular reference
Entry.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.Entity
Entry.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