jocull
jocull

Reputation: 21095

Deep serialization of LINQ to SQL objects

I'm trying to serialize some LINQ to SQL generated objects to use with Memcached if at all possible. I am working on a new transcoder that will act as a new serializer to use with LINQ to SQL objects.

It seems like the DataContractSerializer classes do not event attempt to serialize relational entities. For example, if I had this relationship:

Course -> CourseProject -> Project

The properties for Course would be serialized, but the relational lists for CourseProject and Project would be empty, rather than null. I'm pretty sure this is a function of "Unidirectional" serializing to avoid cyclic redundancies.

Here is the problem with this... when loading items from cache the relationships are totally empty (empty IEnumerable, not null), and will never be loaded from the database if requested. Effectively, all projects for a course are lost when retrieving from cache.

If I could serialize even one or two levels deeper than just the base object it would be helpful. If that's not possible, then I would even prefer that unserialized data was lazy loaded from the database instead, though that sort of defeats the purpose of serializing and caching.

Upvotes: 0

Views: 1438

Answers (2)

jocull
jocull

Reputation: 21095

Alright, after several days of banging my head on my desk I think I have this figured out to a workable level... there was a few things I really had to get a grasp on first:

  1. LINQ to SQL does a lot of its relationship building through lazy loading. Things are loaded on demand.
  2. There are two entity contexts: Attached and Detached. In the detached state, object changes are not tracked, and relationships are not lazy loaded (as there is no data source linked to the entity).
  3. Deserialized objects are always detached, thus will have no related properties.

So the real trick is to just make sure that you attach the object(s) as soon as you deserialize them. Here is a real world example from our system (we have some memcached wrappers in place for our system).

string cacheKey = "apSpace_GetActiveSpacesForPerson_PersonID:" + personid;
List<apSpace> list = MemCached.Get<List<apSpace>>(cacheKey);
if (list == null)
{
    //Some complex, intensive query...
    list = (from s in BaseDB.apSpaces
            from so in BaseDB.apSpaceOwners
            from sp in BaseDB.apSpacePersons
            where (so.PersonID == personid
            && so.SpaceID == s.SpaceID
            && s.Deleted == false
            && s.IsArchived == false)
            || (sp.PersonID == personid
            && sp.SpaceID == s.SpaceID
            && s.Deleted == false
            && s.IsArchived == false)
            select s).Distinct().OrderBy(s => s.Name).ToList();
    //Cache the query result
    MemCached.Store(cacheKey, list);
}
else
    BaseDB.apSpaces.AttachAll(list); //Attach immediately!

return list;

Obviously this is not a silver bullet as far as caching related data, but it does allow you to preserve relationships and at the same time cache the initial result of intensive query. LINQ to SQL would be lazy-loading everything after that regardless.

Upvotes: 1

smartcaveman
smartcaveman

Reputation: 42246

I handled this problem by creating a generic wrapper which I used to lazy-load associations:

 public class SqlRecord<T>{
     where T: ISqlDataObject

     public TAssociationRecord GetOneToOneAssociation<TAssociationRecord>(Expression<Func<T,TAssociationRecord>>> getAssociationPropertyExpression)
          where TAssociationRecord : ISqlDataObject
     {
     } 

     public IEnumerable<TAssociationRecord> GetOneToManyAssociation(Expression<Func<T,EntitySet<TAssociationRecord>>> getAssociationPropertyExpression)
         where TAssociationRecord: ISqlDataObject
     {
     }

 }

Then I added a marker interface to all of the data objects that were automatically generated by LINQ to SQL:

  public interface ISqlDataObject {}

I bet you could do something similar to meet your needs.

Upvotes: 1

Related Questions