Database Aspects of Code First
Using Entity Framework (EF) 4.1 with the Code First approach seems like a good choice for my greenfield Web App – during development I can focus on the Web and C# .Net paradigms, and postpone the bulk of the database work until such time my application and data model has stabilised. That said there are several database aspects I must still address up front, including:
- Decide and implement the high-level mapping between .Net classes and database tables. Morteza Manavi (blog | twitter) has a great series on this.
- Set field properties such as string length, required vs. optional etc., which will get reflected in the database.
Since I plan to continue using Code First beyond the initial project setup, populate with test data etc. it becomes desirable to make further changes to the database beyond what EF Data Annotations and the Fluent API supports natively. These changes are usually put in the Seed() method of the inherited DbContext class; this method gets executed immediately after the database has been created.
EF 4.1 does not support adding unique keys, so I create them with a call to ExecuteSqlCommand() as follows:
context.Database.ExecuteSqlCommand(@"
ALTER TABLE [WebApp].[MyTable] ADD CONSTRAINT
[UK_MyTable_Name] UNIQUE(Name);
ALTER TABLE [WebApp].[MySecondTable] ADD CONSTRAINT
[UK_MySecondTable_Name] UNIQUE(Name);
");
For straight forward SQL I prefer the above approach since it will allow me to later (if and when the database takes on a life of its own) add the SQL text to the database creation script generated by EF.
Key Names
While foreign keys generated by EF have names that can also be overridden, I notice that EF has not given the primary keys any names, and there is also no EF way of setting their names. SQL Server in turn auto generates primary key names such as PK__MyTable__0376AC45EB0148EF. Apart from the ugly GUID in the name, the MyTable part unfortunately gets truncated at 8 characters, so I decide to add proper names to the primary keys.
Ladislav Mrnka (stackoverflow) was kind enough to point me to his sample code for dropping and recreating primary keys using SQL. Since a database “with a life of its own” would not need this drop-create SQL code, I prefer in this case to stay in .Net land and use SQL Server Management Objects (SMO) for implementing the changes:
SMO + LINQ + EF = True
Using SMO requires adding a few references to the project as described here. Discovering and altering primary keys is quite straight forward, and I elect to use Language-Integrated Query (LINQ) instead of nested foreach statements,
which makes for nicely concise code. Here’s the complete Seed() method:
// Seed() method from the MyDbContext : DbContext class
public void Seed(WebAppEntities context)
{
// Add unique keys
context.Database.ExecuteSqlCommand(@"
ALTER TABLE [WebApp].[MyTable] ADD CONSTRAINT
[UK_MyTable_Name] UNIQUE(Name);
ALTER TABLE [WebApp].[MySecondTable] ADD CONSTRAINT
[UK_MySecondTable_Name] UNIQUE(Name);
");
// Get a Server object from the existing connection
var server = new Microsoft.SqlServer.Management.Smo.Server(
new Microsoft.SqlServer.Management.Common.ServerConnection
(context.Database.Connection as System.Data.SqlClient.SqlConnection));
// Get a database object. Qualify names to avoid EF name clashes.
Microsoft.SqlServer.Management.Smo.Database database =
server.Databases[context.Database.Connection.Database];
// Rename auto generated primary key names
(
from Microsoft.SqlServer.Management.Smo.Table table in database.Tables
where table.Schema == "WebApp"
from Microsoft.SqlServer.Management.Smo.Index index in table.Indexes
where index.IndexKeyType ==
Microsoft.SqlServer.Management.Smo.IndexKeyType.DriPrimaryKey
// Create pairs of a name string and an Index object
select new { table.Name, index }
// ToList() separates reading the object model above from modifying it below
).ToList()
// Use extension and anonymous methods to rename the primary keys
.ForEach(primaryKey =>
{
// Name primary keys "PK_TableName"
primaryKey.index.Rename(
"PK_" + primaryKey.Name.Replace("[", "").Replace("]", ""));
primaryKey.index.Alter();
}
}
Summing Up
When my project has more .Net coding than database focus, I often prefer using the power, simplicity and IntelliSense of SMO to perform my database manipulations; maybe you’ll consider doing the same ?-)