Recently I alluded to writing a series of posts on the Entity Framework Futures, now referred to as the “Microsoft ADO.NET Entity Framework Feature Community Technology Preview 5“ which could really, really use a cool codename.
Introduction
If you’ve been following along on the Windows Workflow Rules Engine series of posts (links at the bottom of this article), you’d know that we have a working model sample already underway. It is my plan to reuse this solution to embrace learning about code first and the recent Entity Framework CTP5. I’m going to reuse the objects in that small sample, and demonstrate how to do some initial plumbing to get you up and running.
Here’s a link to the original solution, in case you’d like to see a “before and after” view of the solutions. What we’ll be doing in part 1 is to simply provide a data storage solution underneath the existing data objects. You’ll find that we don’t need to do much to “bolt” the Entity Framework onto the object model and to use a context to persist data to a database – all created through code.
Preparing the Solution
The Data Access Assembly
The first thing I had to do was add another class library (“DataAccess”) to the solution, to contain the actual data context definition and to provide for any helper methods and anything else specific to data persistence. To this new class, I added references to System.Data.Entity and a local reference (relative) to the EntityFramework.dll (which you need to copy from the CTP5 release). We also need a project reference to the BusinessObjects class.
Next, I added a data context class which for this example is very straightforward:
namespace DataAccess
{
public class EmployeeContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
}
}
Unit Test Project
Next, I added a new unit test project (DataAccess.UnitTests) to the solution explicitly to test the data access functionality. I added a reference to System.Data.Entity and also a reference to the EntityFramework.dll and also added a new App.Config file, which I’ll use to explicitly provide connection string information for the Data Context. This project also has project references to BusinessObjects and to the DataAccess assembly, of course.
Business Objects Assembly
I like to try and keep the data objects themselves as separate and clean as possible, so the only change to the existing project is to add a reference to the System.ComponentModel.DataAnnotations assembly. We need this so we can add Data Annotations to the classes for use with the Entity Framework.
The Solution Structure
Changing the Data Object(s)
Now that we have everything pretty much in the right location, we need to modify the Employee class a little bit. Thankfully, this is a fairly easy task. I did a little refactoring to create a one-to-many relationship between an Employee and his/her manager, as well as a reverse navigation relationship, Manager to Employees. Referencing System.ComponentModel.DataAnnotations allows us to decorate the class with attributes which the Entity Framework will use to create the database schema.
Here is the new and improved Employee entity. You can see the data annotations – there was a small catch with the navigation properties, modelled as self joins, there seems to be a bug in CTP5 so the only realistic option right now is to go with Code First’s default column. I couldn’t guarantee the use of Manager/Employees, so instead it has to be tested with a unit test for now.
namespace BusinessObjects
{
public class Employee
{
[Key]
public int EmployeeNumber { get; set; }
[StringLength(100)]
public string FirstName { get; set; }
[StringLength(100)]
public string Surname { get; set; }
[StringLength(100)]
public string JobTitle { get; set; }
//Enums are not supported!!?
//http://social.msdn.microsoft.com/Forums/en-US/adonetefx/thread/9a5d5a64-4de8-4685-8896-c5e8f66fda65
public StateEnum Location { get; set; }
[Required]
public DateTime DateHired { get; set; }
// we’ve got a bug here with self-joins
//http://social.msdn.microsoft.com/Forums/en-US/adonetefx/thread/05198b97-f178-49ba-91da-7a2516a9ad8d
// for now, we have to accept the default column generated by code-first
public virtual Employee Manager { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
}
Some notes on entity support..
- Self joins are still a bit of a mess
- No support for enums! Bitterly disappointing really.
- the Fluent API can be used to add extra annotations at runtime
Unit Test Configuration
In our unit tests, we can add the following method to ensure that the database model is dropped and recreated anytime the schema changes. You’ll need a more practical solution for any real product work, of course.
[ClassInitialize]
public static void TestInitialize(TestContext testContext)
{
DbDatabase.SetInitializer<EmployeeContext>(new
DropCreateDatabaseIfModelChanges<EmployeeContext>());
}
Finally, I’ve added an app.config to the unit test project, and in it, I’ve added a connection string which uses the same name as the Data Context. This allows us to specify the connection settings (the default is to use SQL Express).
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<!–
http://blogs.msdn.com/b/adonet/archive/2010/09/02/ef-feature-ctp4-dbcontext-and-databases.aspx
–>
<add
name="EmployeeContext"
providerName="System.Data.SqlClient"
connectionString="Server=.;Database=Employee;Integrated Security=True" />
</connectionStrings>
</configuration>
Basically, every time the tests are run, the model will be dropped and recreated. It’s also a good time to seed test data for the unit tests.
Putting it all together
So to test out Code First for the first time, I’ve mocked up a very simple test which is almost verbatim from the sample linked below. Please note that this is for demonstration purposes only and does not constitute a valid unit test approach – I’m merely demonstrating how Code First works.
[TestMethod]
public void TestAddData()
{
using (var db = new EmployeeContext())
{
// Add an employee
var john = new Employee { EmployeeNumber = 1,
FirstName = "John",
Surname = "Smith",
DateHired = DateTime.Now.AddYears(-1) };
db.Employees.Add(john);
int recordsAffected = db.SaveChanges();
Trace.WriteLine(String.Format("Saved {0} entities to the database",
recordsAffected));
}
}
That’s pretty easy. If we execute the test and everything is configured correctly, we should get the database created, and the single row inserted:
If we add a second test, we can add another record and include a relationship to the first row added in our initial test:
[TestMethod]
public void TestAddRelationshipsData()
{
using (var db = new EmployeeContext())
{
Employee john = db.Employees.Where(x => x.EmployeeNumber == 1).First();
// Add an employee
var gary = new Employee { EmployeeNumber = 2,
FirstName = "Gary",
Surname = "Smith",
DateHired = DateTime.Now.AddYears(-1),
Manager = john};
db.Employees.Add(gary);
int recordsAffected = db.SaveChanges();
Trace.WriteLine(String.Format("John has {0} Employees",
john.Employees.Count));
}
}
Notice that we reload the first employee and specify the relationship when constructing the second employee. Easy stuff!
Obviously this is for demonstration purposes only! You should always encapsulate your test data either loaded as a static set (and cleaned up), or use a transaction scope to prevent any permanent commits to the database – unit tests should never leave any test data persisted!
Conclusion
That’s pretty much the rundown for Part 1. We’ve seen how we can bolt EF’s code first onto an existing (albeit shallow) model, and programmatically cause the schema to be created. There’s so much more to do though, and (as I’m frequently finding out) there are a lot of roadblocks to overcome, but thus far it is promising!
We’ll continue with Part 2 next week, once I’ve had a chance to explore more advanced concepts – mostly to do with security, configuration and indexing and so forth.
If you are interested in the updated solution files, please leave a comment and I’ll happily post them to this article.
Further Reading
Entity Framework CTP 5
[ http://www.microsoft.com/downloads/en/details.aspx?FamilyID=35adb688-f8a7-4d28-86b1-b6235385389d ]
Code First Walkthrough
[ http://blogs.msdn.com/b/adonet/archive/2010/12/14/ef-feature-ctp5-code-first-walkthrough.aspx ]
More on configuring Data Context
[ http://blogs.msdn.com/b/adonet/archive/2010/09/02/ef-feature-ctp4-dbcontext-and-databases.aspx ]
The fluent API samples
[ http://blogs.msdn.com/b/adonet/archive/2010/12/14/ef-feature-ctp5-fluent-api-samples.aspx ]