views:

374

answers:

4

Here's a little experiment I did:

MyClass obj = dataContext.GetTable<MyClass>().Where(x => x.ID = 1).Single();
Console.WriteLine(obj.MyProperty); // output = "initial"
Console.WriteLine("Waiting..."); // put a breakpoint after this line
obj = null;
obj = dataContext.GetTable<MyClass>().Where(x => x.ID = 1).Single(); // same as before, but reloaded
Console.WriteLine(obj.MyProperty); // output still = "initial"
obj.MyOtherProperty = "foo";
dataContext.SubmitChanges(); // throws concurrency exception

When I hit the breakpoint after line 3, I go to a SQL query window and manually change the value to "updated". Then I carry on running. Linq does not reload my object, but re-uses the one it previously had in memory! This is a huge problem for data concurrency!

How do you disable this hidden cache of objects that Linq obviously is keeping in memory?

EDIT - On reflection, it is simply unthinkable that Microsoft could have left such a gaping chasm in the Linq framework. The code above is a dumbed-down version of what I'm actually doing, and there may be little subtleties that I've missed. In short, I'd appreciate if you'd do your own experimentation to verify that my findings above are correct. Alternatively, there must be some kind of "secret switch" that makes Linq robust against concurrent data updates. But what?

+3  A: 

This isn't an issue I've come across before (since I don't tend to keep DataContexts open for long periods of time), but it looks like someone else has:

http://www.rocksthoughts.com/blog/archive/2008/01/14/linq-to-sql-caching-gotcha.aspx

stusmith
+1 OMG, it really is a Gotcha! Gotta add that to the C# Gotcha KB...
Shaul
Worse still, if you follow his advice there and set ObjectTrackingEnabled = false, you can only read from the DB; you can't do SubmitChanges! That REALLY makes life difficult!
Shaul
Here's a link to the C# gotcha KB: http://stackoverflow.com/questions/241134/what-is-the-worst-c-net-gotcha
Shaul
Hai your link is dead, can you find and update?
Will
+2  A: 

Set the ObjectTrackingEnabled property of the DataContext to false.

When ObjectTrackingEnabled is set to true the DataContext is behaving like a Unit of Work. It's going to keep any object loaded in memory so that it can track changes to it. The DataContext has to remember the object as you originally loaded it to know if any changes have been made.

If you are working in a read only scenario you should turn off object tracking. It can be a decent performance improvement.

If you aren't working in a read only scenario then I'm not sure why you want it to work this way. If you have made edits then why would you want it to pull in modified state from the database?

Mike Two
Ya, but I'm not working in a read-only environment. Any other ideas?
Shaul
This is by design. It's designed for you to pull in state, modify and the write back using optamistic concurrency. You could call Refresh on the DataContext but that could kill any changes you made. It isn't meant to provide real time syncing. Real time syncing and editing don't go together well.
Mike Two
+1  A: 

LINQ to SQL uses the identity map design pattern which means that it will always return the same instance of an object for it's given primary key (unless you turn off object tracking).

The solution is simply either use a second data context if you don't want it to interfere with the first instance or refresh the first instance if you do.

DamienG
+2  A: 

LinqToSql has a wide variety of tools to deal with concurrency problems.

The first step, however, is to admit there is a concurrency problem to be solved!

First, DataContext's intended object lifecycle is supposed to match a UnitOfWork. If you're holding on to one for extended periods, you're going to have to work that much harder because the class isn't designed to be used that way.

Second, DataContext tracks two copies of each object. One is the original state and one is the changed/changable state. If you ask for the MyClass with Id = 1, it will give you back the same instance it gave you last time, which is the changed/changable version... not the original. It must do this to prevent concurrency problems with in memory instances... LinqToSql does not allow one DataContext to be aware of two changable versions of MyClass(Id = 1).

Third, DataContext has no idea whether your in-memory change comes before or after the database change, and so cannot referee the concurrency conflict without some guidance. All it sees is:

  • I read MyClass(Id = 1) from the database.
  • Programmer modified MyClass(Id = 1).
  • I sent MyClass(Id = 1) back to the database (look at this sql to see optimistic concurrency in the where clause)
    • The update will succeed if the database's version matches the original (optimistic concurrency).
    • The update will fail with concurrency exception if the database's version does not match the original.


Ok, now that the problem is stated, here's a couple of ways to deal with it.

You can throw away the DataContext and start over. This is a little heavy handed for some, but at least it's easy to implement.

You can ask for the original instance or the changed/changable instance to be refreshed with the database value by calling DataContext.Refresh(RefreshMode, target) (reference docs with many good concurrency links in the "Remarks" section). This will bring the changes client side and allow your code to work-out what the final result should be.

You can turn off concurrency checking in the dbml (ColumnAttribute.UpdateCheck) . This disables optimistic concurrency and your code will stomp over anyone else's changes. Also heavy handed, also easy to implement.

David B