Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq caching data values - major concurrency problem?

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?

like image 846
Shaul Behr Avatar asked Sep 30 '09 15:09

Shaul Behr


2 Answers

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

like image 192
stusmith Avatar answered Sep 21 '22 01:09

stusmith


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.

like image 23
Amy B Avatar answered Sep 20 '22 01:09

Amy B