D3: Fun with Concurrency and Model First – Part 1
The second D3 release is now up at: d3-0.0026.zip It’s the fruit of my labors pretty much full-time this week because it has been “App Week” on the EF team this week.
What a great time! My kids got out of school for summer break last Friday (which means I have the option of sleeping in rather than waking up early to drive the car pool), and this week I got to almost completely clear my calendar and spend time working on DPMud. App week is a time we set aside periodically to devote as much of our attention as possible to using the EF and related technologies to build applications so that we can experience some of the joys (and pain) of our valued customers. Most everyone looks forward to it, and we invariably find lots of great opportunities to improve the product.
While most other folks have been building new applications from scratch, I devoted my week to concentrated time on my long-running favorite app and just picked the next task off my to-do list and went to work. That task was to add a concurrency-token property to each of my entities and a simple test to verify that optimistic concurrency checks are working correctly. Seems pretty simple right? The model currently only has 4 entities, so how hard could it be? Can you say “working all week on this one task”? :-(
I won’t claim to be a test-first kind of developer, and the test code below is something I wrote after I had things working rather than first, but I’ll show it to you first so you can capture the goal. Then we’ll talk through the challenges I encountered and how I conquered them (in the hope that I can save you some pain). If you remember from my post about the last release, each entity currently in the model has a similar set of scalar properties but different relationships. My idea was to just add a Version property which would be marked as a concurrency token and setup to be automatically updated in the database any time any property on the entity changes so that we would get automatic concurrency checks. This property should actually have protected visibility because I don’t want to interact with it directly when using my entities—I just want the EF to use it and maintain it for me. The result would enable this test helper method:
private void VerifyConcurrency<T>(Func<T> create, Action<T> modify) where T : class
{
using (var ctx = Utilities.CreateTestContext())
{
var objectSet = ctx.CreateObjectSet<T>();
var obj = create();
objectSet.AddObject(obj);
ctx.SaveChanges();
using (var ctx2 = Utilities.CreateTestContext())
{
var key = ctx.ObjectStateManager.GetObjectStateEntry(obj).EntityKey;
var obj2 = (T)ctx2.GetObjectByKey(key);
modify(obj2);
ctx2.SaveChanges();
}
modify(obj);
ctx.SaveChanges();
}
}
I made the method generic and gave it two delegate parameters so that I can easily re-use it for each entity type I currently have plus extend it as I add more types over time. I also designed the code so that it will work with POCO types since I anticipate switching D3 to use POCO classes in the near future. As a part of this release I also introduced a simple utility method which creates the D3Context with a connection string to my little test database—this way it’s quick and easy to type since intellisense finds things for me.
The basic pattern in this method is that it uses one context to add an entity to the database. I use the create delegate because different entities have different requirements as far as what must be set on them to make them persist successfully. Each of our current entity types requires a non-null Name property, for instance, but while the Room entity doesn’t have any other required properties, an Actor must have a non-null Room reference, etc.
Once the new entity is added, I then create another context instance (which means another connection to the DB, etc.) and on it retrieve another copy of that same entity. I do this retrieval by having the state manager in the first context lookup the entity for me by reference (works for POCO) and then give me back the EntityKey corresponding to it (also works for POCO even though the entity itself doesn’t have an EntityKey property). Then I can use that key with the second context to load another copy of the entity, and call the modify delegate which makes a unique update to some property on the entity and saves that update to the database.
At this point the first instance of the entity is being tracked by the first context, and it’s concurrency token (assuming everything is configured correctly) represents the state of the entity before the second context updated anything. So we call the modify delegate on this entity and SaveChanges which throws an OptimisticConcurrencyException because the concurrency token in the database doesn’t match what’s in this context.
In the actual test, I create a list of Action types which call this method specifying the appropriate type and passing in lambdas for the create and modify delegates. Then it just loops over that list running the action and verifying that each call produces the appropriate exception. You can look at the file Tests\Concurrency.cs in the release if you want the full details.
The point of all this, though, is that to make this test pass I needed my entities to have concurrency properties that were configured properly in the database. With recent versions of SQL Server, the best way to accomplish that is with a RowVersion column (previously called a timestamp). This special column type is stored as an 8 byte binary, and the server guarantees that it will have a unique value any time any part of a row in its table is modified. The trick is that this special type only exists in the server—it’s not available in the CLR or the EDM conceptual model. If I were reverse engineering a model from the database, that would be no big deal. The tools would automatically map the RowVersion column in the database to an 8 byte binary property in my model. In this case, though, I’m using model first to generate the database from my model, and when I create the property, model first has no way to know that it should create a RowVersion type rather than a binary type in the DB.
So it was time to customize the model first process again, but before I got even that far I ran into a bug. Ahhh… The joy of AppWeek and of beta software. The issue is that if you set Concurrency Mode to Fixed on any of your entities in VS 2010 beta 1, then when you choose Generate Database Script from Model, the tool will display an error rather than generating the script. Considerable debugging and discussion with the developer who did most all of the work for model first later, we were able to track the problem down to a one line problem where model first was assuming that all property facets that could be set in the conceptual model were at least valid possibilities for the storage model, when in fact Concurrency Mode is the one property which can only be in the conceptual model and not the storage model. So the workflow activity that generates the SSDL from the CSDL was triggering an exception from the metadata system.
This bug will be fixed in beta 2, but that doesn’t help us much for D3 right now. So my final solution was to extract a small piece of the model-first workflow into a separate DLL, make a fix to the code and then modify the DbGen.xaml file which describes the workflow to call out to that DLL rather than the core version that ships with the product. The final result is part of this release, and it’s called modelfirst-bugfix.dll. If you go the misc directory under the release and run the batch file install-bugfix.cmd it will copy the DLL to a directory in your VS installation (make sure you run this from a VS command prompt where the DevEnvDir environment variable is set). Naturally this little hack is completely unsupported, but it works for me, and it might work for you too if you need to use model first with concurrency and beta 1 of EF4.
Now at least I can run model first when I have concurrency configured, but I’ve still got the problem that my properties come through as binary rather than RowVersion type in the DB. That’s where I’ll pick up the next post… Until then, happy coding.
- Danny