OK folks, after way too much delay, I’ve finally gotten all my ducks in a row and begun the process of rewriting DPMud. Since my goal is to completely rebuild it from scratch, the process is going to take a while, and I intend to let you all look over my shoulder a bit as I go. So don’t expect too much from this first release—all we have is a basic solution with a very simple model and some unit tests. You can download the release from: d3-0.0010
First, let’s take a look at a few decisions that came up along the way, and then we’ll dig into some specific code areas.
Like every other part of the system, we’re going to start with a simple model and expand. Here’s a first look:
A few things to notice:
The feature we call model first shows up in the entity designer (aka Escher) as the option “Generate Database Script from Model” on the context menu when you right click on an open part of the designer surface. Out of the box this will generate a SQL DDL script that you can use to create a database for persisting the entities in your model. It also automatically generates the SSDL and MSL which corresponds to that model. This is great because it means that I can concentrate on the thing that is important to my program, the conceptual model. If I make a change to the model, once that change is complete, I’m done. I don’t have to also modify the database and the mapping to reflect that change. Since I’m also generating entity classes from the model, I get a nice “DRY” (Don’t Repeat Yourself) development experience even though various parts of the system do have repetition, I don’t have to engage in that repetition myself. Naturally we want to have an even more DRY experience, and code-first will lead us there for folks who want to do everything in code, but for the design-time experience this is not half bad.
Naturally, though, there are times where I want more control over the database generation process, which is why there are customization hooks built-in. The process of generating the database is orchestrated by a customizable workflow which has a couple of activities that manage generating an MSL and SSDL for the CSDL and then generating SQL from that SSDL. This second step (SSDL->SQL) is further customizable because it is accomplished with a T4 template. Eventually we will take advantage of these customization mechanisms to allow us to automatically generate rowversion columns for concurrency, constraints that keep item relationships straight and other things.
All of this seemed somewhat academic, though, for the first few phases of the project until I started thinking through my testing strategy. Eventually we’ll test parts of the system using fakes and mocks and the like, but we also need integration tests that send data all the way down to the database and back, and I wanted to start writing those tests right away and deliver them with my initial stab at the model. To make that work I really wanted APIs for creating and dropping the database somewhat like we can get from L2S so that my tests can create a temp version of the database (separate storage but same schema) and test pushing data in and out of it. Eventually this will also be useful for deployment, import/export of data, version-to-version upgrades and the like.
So, I decided to customize the db generation process now by modifying the SSDL->SQL template to generate a C# method for creating the database schema rather than a SQL script. The process to set things up for customization looks like this:
Now when I choose the option to generate the database it runs the workflow as specified in my project which then uses the template from my project rather than the default versions.
Next, I modified the template file to output C# code rather than SQL. You can find the template file in the release and do your own diff against the default template, but the basic approach was to wrap each batch of SQL statements up to a “GO” in an ado.net command with a C# @”” string and execute it. I eliminated a chunk of the template that would drop existing tables because my approach is to drop the whole database and recreate it, and I put all of these execute statements into a method on an internal partial class that I added to my model DLL. That way most of that class is defined outside the template but this one method can be defined by the code generated from the template. Then I further created a partial class file for my generated EF context and added methods to check for existence of the database, drop the database, and create it (using the generated method).
One current limitation in the designer is that the generate database command always by default creates a file with a name based on the name of the EDMX file and with SQL for the extension. I’ve already started discussions with the designer folks about adding some customization hooks here, but for now you just have to remember that whenever you generate the database you should rename the file.
All of this enabled me to write the following simple test:
[TestMethod]public void DropAndCreate(){ using (var ctx = new D3Context("name=D3TestContext")) { ctx.DropDatabase(); Assert.IsFalse(ctx.DatabaseExists()); ctx.CreateDatabase(); Assert.IsTrue(ctx.DatabaseExists()); }}
var builder = new SqlConnectionStringBuilder(((EntityConnection)entityConnection).StoreConnection.ConnectionString);
Then we store the initial-catalog property into a string field so we know the name of the database we should be working with, and then we replace the initial-catalog with “master” since that database is present on every SQL instance.
The constructor then extracts the new connection string from the builder and uses it to create a new SqlConnection instance. We use this connection to execute commands without directly connecting to the database. The class also implements IDisposable so that its Dispose method can Dispose of the SqlConnection.
The other interesting trick which came up was the fact that while my initial executions of the above test succeeded while stepping through in the debugger, running the whole test straight through failed because the first part of the DropDatabase method executes a command to check if the database exists, and after that the drop command would fail with an error saying that the database was in use. Eventually I fixed this by adding a ClearAllPools method to the class which closes the connection, calls SqlClient.ClearAllPools() to make sure that there are no connections in the connection pool holding onto the database and then re-opens the connection. This method is called after the existence check and before the command to drop the database.
We’re finally off and running! We have an initial model, a simple way to deploy the database and some initial tests. (Those tests have already found a couple of issues I had in my first crack at the model, by the way. So the testing is already paying off. Yay!) Next I’ll probably tackle some further customizations of the generated database. Soon we’ll look at replacing the default code gen with a POCO template, and in the not distant future I hope to fill out more pieces of the architecture so we can get a small end-to-end slice going with a WCF service and Silverlight, but all in good time…
- Danny