Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

LINQ to SQL ObjectTrackingEnabled and Deferred Loading of Related Data


:P
On this page:

Today I've been playing around with my business object implementation which acts as a wrapper around the data context. It holds a reference to the data context and all operations that occur in the business object generally are tied to this data context.

LINQ to SQL by default enables object tracking on each of the objects it returns from a query, which is not necessary though if you are truly just returning query results. So I figured it might be a good idea to add another ReadOnlyContext to the business object and explicitly allow turning off object tracking to improve performance and overhead.

At first this worked well, but I started running into odd problems where the full context works and the ReadOnlyContext would not. A number of these were my fault - I was modifying the query results and applying the non-read-only  context to the queries. But a few other queries mysteriously failed without much of a clue of what was going on.

It turns out I've run into another oddity in LINQ to SQL in that ReadOnly context's cannot deal with deferred loading of entities.Before I found out I ended up simplifying the problem to a single query operation to demonstrate/

In the following query I retrieve a set of Entry objects for a time tracking app. The entries have related project entities (1-1 relation). If I run the following query with object tracking enabled:

TimeTrakkerContext context = new TimeTrakkerContext();
 
IQueryable<EntryEntity> q =
   from ent in context.EntryEntities
   where ent.UserPk == 1
   orderby ent.TimeIn descending
   select ent;
 
Response.Write(q.ToList()[0].ProjectEntity.ProjectName);

I get the right result. The project entity is filled and the project name returned.

But simply adding the ObjectTrackingEnabled flag and setting it to false like this:

TimeTrakkerContext context = new TimeTrakkerContext();
context.ObjectTrackingEnabled = false;
 
IQueryable<EntryEntity> q =
   from ent in context.EntryEntities
   where ent.UserPk == 1
   orderby ent.TimeIn descending
   select ent;
 
// fails here - Object Reference not set to an instance of an object
Response.Write(q.ToList()[0].ProjectEntity.ProjectName);   

makes this very same query fail to retrieve the project entity properly.

It turns out that this is a known issue that has been reported on Connect before. Specifically here's a quote from Microsoft's Alex Turner from this Connect Bug report:

Without Object Tracking, each time you request a Customer from an Order, we can't tell whether we've already made an object for that Customer when navigating some other Order, and so we'll have to make a new Customer each time. Therefore, when Object Tracking is disabled, we do not allow navigation over relationships that would cause further queries to be executed, and thus further objects to be materialized, perhaps for the second time.

he goes on to suggest to either run with object tracking on or turn on eager loading.

The solution is either to leave Object Tracking enabled, or to use Eager Loading by setting LoadWith clauses on a DataLoadOptions object you give to your DataContext. With Eager Loading, you can tell LINQ to SQL that when you retrieve a set of Orders with Object Tracking off that you want to also get each Order's Customer object at the same time. This way, you can maintain Object Identity and a healthy object graph, even without Object Tracking.

Hmmph... that sure puts a damper on using a ReadOnly DataContext.

So at this point I'm reevaluating my use of a ReadOnlyContext in my application. I suppose it makes sense in true one level queries but for complex queries where relationships are required we're screwed.

Add that as another oddity of LINQ to SQL to the slew of inconsistent behaviors that  LINQ to SQL exhibits...

Incidentally I decided to a little performance checking on the difference between using ObjectTrackingEnabled and not after I got into this a little further and while there's a small bit of overhead in terms of processing time, it's minute. As long as entities aren't updated there's not a lot of overhead. The penalty is mainly in additional startup code for the query, which is relatively small.

Given that having both a regular and read-only context can be confusing as there are other instances where the business object helper classes internally use the context to perform additional tasks such as data conversion. With two types of context there need to be overloads or separate objects to handle that scenario and it's not always clear to the consumer of a business method whether he's received a ReadOnly context or the Full context.

It sure would be nice that if an IQueryable (or maybe it should be IDataQueryable) could get a reference to the actual active DataContext that created the query - that would facilitate this scenario drastically. But alas this deferred loading behavior really puts the nail in the coffin of this whole idea anyway.

Single full context it is.

Posted in .NET  LINQ  

The Voices of Reason


 

Jason
April 12, 2008

# re: LINQ to SQL ObjectTrackingEnabled and Deferred Loading of Related Data

No offense Rick, but this sounds like a case of pre-mature optimization. I think you may have discovered this on your own: adding additional complexity to your app in the name of performance when there is not a performance problem is seldom a good idea.

On the bright side, you learned another limitation of Linq to SQL and passed on to all of us to from which to learn. Thanks for that!

Rick Strahl
April 13, 2008

# re: LINQ to SQL ObjectTrackingEnabled and Deferred Loading of Related Data

Jason - you're right. But remember that this code was supposed to live in framework level code and so optimizations at this level - even if small - have cumulative effects. I'm willing to experiment with those kind of scenarios even if it turns out as in this case that the optimizations increase complexity and maybe more importantly don't work the same as the unoptimized code...

If you don't test for perf you'll never know whether it's premature optimization or not. <g>

Benjamin Eidelman
April 14, 2008

# re: LINQ to SQL ObjectTrackingEnabled and Deferred Loading of Related Data

Rick,

That's an interesting point, I think this is a little improved in Entity Framework, disabling change tracking and disabling identity cache separetely. I agree with you that at a Framework level you can't take those assumptions.

Just an observation, "With two types of context there need to be overloads or separate objects to handle that scenario "

Actually it's no necessary to use a DataContext with and only with it's own Entities,
if you accept to use the GetTable method you can load entities in any dbml with any DataContext, like this:

Order[] orders = readOnlyDataContext.GetTable<Order>().Where(...).ToArray();

or

Order[] orders = trackingDataContext.GetTable<Order>().Where(...).ToArray();

where "Order" could be in an independant dbml

And there's other reasons to separate your Entities from your DataContext(s) in separate files, like improving Source Code Control (you don't want a developer to checkout the whole data model every time), and separating your data model from your data access code.



Regards!,

E Rolnicki
April 17, 2008

# re: LINQ to SQL ObjectTrackingEnabled and Deferred Loading of Related Data

Hmm...have you tried

context.DeferredLoadingEnabled = false; ?

Matt
May 16, 2008

# re: LINQ to SQL ObjectTrackingEnabled and Deferred Loading of Related Data

The problem I'm encountering is I don't want the the objects cached - I have a scenario where the database data is being updated by a different entity, but the changes are not showing up in my app because my context caches the results if ObjectTracking is enabled, and it won't hit the database for an update.

Monty
March 19, 2009

# re: LINQ to SQL ObjectTrackingEnabled and Deferred Loading of Related Data

Why is it that every time I have a question, Rick Strahl's blog already has the answer? Keep bloggin' man!
 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2019