Finally! I’ve been itching to get into the latest incarnation of the EF for a while now and finally I had the opportunity to take it around the block (kick the tires and generally rough it up). My main interest in EF 2.0 was the supposed support for Unit Testing and the improvements on using POCO to map to the Data Model.
I have to admit, although I liked EF 1.0 when I first started using it, one of my biggest bug bears was the fact that you had to “disconnect” your entities before you could really work with them outside the context (no pun intended) of the Entity Framework. I was also quite miffed when I discovered there was no easy way to mock the underlying data layer so I ended up with code like this:
/// <summary>
/// Loads a customer instance with the relevant information from the database.
/// </summary>
/// <param name="i_customerId">The customerId of the customer data to be retrieved.</param>
/// <param name="o_customer">The customer instance to be created.</param>
public void Load(string i_customerId, out Customer o_customer)
{
if (string.IsNullOrEmpty(i_customerId))
{
throw new ArgumentException("Parameter cannot be null.", "i_customerId");
}
int numericVal;
if (!int.TryParse(i_customerId, out numericVal))
{
throw new ArgumentException("Parameter cannot be non-numeric.", "i_customerId");
}
if (numericVal < 0 || numericVal > 9999)
{
throw new ArgumentOutOfRangeException("i_customerId");
}
m_customerRepository.Load(i_customerId, out o_customer);
if (o_customer != null)
{
if (!string.IsNullOrEmpty(o_customer.ContactName) && o_customer.ContactName.Contains(" "))
{
o_customer.ContactName = o_customer.ContactName.Trim(' ');
string[] names = o_customer.ContactName.Split(' ');
if (names.Length > 1)
{
names[names.Length - 1] = names[names.Length - 1].ToUpper();
}
o_customer.ContactName = string.Join(" ", names);
}
}
}
In this case, m_customerRepository is the injected ICustomerRepository instance. When we look at the implementation of the actual data layer class (which does not get tested by the Unit Test, we find that inside the Load method we have the following:
/// <summary>
/// Loads a customer instance with the relevant information from the database.
/// </summary>
/// <param name="i_customerId">The customerId of the customer data to be retrieved.</param>
/// <param name="o_customer">The customer instance to be created.</param>
public void Load(string i_customerId, out BaseCustomer o_customer)
{
Customer customer = (CustomerSet.Where(cust => !string.IsNullOrEmpty(cust.CustomerID) &&
cust.CustomerID == i_customerId)).First();
if (customer != null)
{
o_customer = new BaseCustomer()
{
Address = customer.Address,
City = customer.City,
CompanyName = customer.CompanyName,
ContactName = customer.ContactName,
ContactTitle = customer.ContactTitle,
Country = customer.Country,
CustomerID = customer.CustomerID,
Fax = customer.Fax,
Phone = customer.Phone,
PostalCode = customer.PostalCode,
Region = customer.Region
};
}
else
{
o_customer = null;
}
}
Not the best way of doing things, that is for sure! In fact it is downright ugly (IMHO). So when I heard that there were improvements to the EF for .NET 4.0, especially in the area of Unit Testing I was curious to say the least. As I delved in deeper I started finding more and more things that made it more attractive to my style of development. For example one of the beauties of EF 2.0 is the fact that you can remove the CustomTool that generates the entity classes that are bound to the data model (through the edmx file). When you do this, you effectively get rid of the code generation for the EF instance that you have loaded in your project. There are some excellent examples (and walkthroughs available) from the ADO.NET Team blog:
POCO in Entity Framework : Part 1 – The Experience (excellent walkthrough on removing the CustomTool)
POCO in Entity Framework : Part 2 – Complex Types, Deferred Loading and Explicit Loading
POCO in Entity Framework : Part 3 – Change Tracking with POCO
So now what? Great! So now I can use my POCO to update the Data Model. But I still hadn’t found out how to do true unit testing with DI and mocking? I was quite flummoxed until I realized (with a helpful pointer from a friend at Microsoft – thanks Jason) that the answer was staring me in the face:
“Can’t you create a mock class that derives from IObjectSet instance yourself, or is there a problem doing that?”
Well yes, I did have a problem with that – it meant that I would have to write more code. I was naively hoping to have something like:
List<Customer> cusList = TestHelper.CreateCustomerList();
IObjectSet<Customer> context = cusList.AsObjectSet();
So I was being lazy… I guess that, with each version of .NET, I had become more and more accustomed to so much being done for me that stumbling across something as “simple” as creating an AsObjectSet() function that was not available was bit of a shock. More so when you look at what IS available on a List<entity> method / property list.
At first I contented myself with just doing what was obvious – create a mock class that inherited from IObjectSet<Customer>, before I realized (with another push from Jason) that I could make it more generic and have a MockObjectSet<T> class:
internal class MockObjectSet<T> : IObjectSet<T>
where T : class
{
public MockObjectSet(List<T> entityList)
{
if (entityList == null)
{
throw new ArgumentNullException("entityList");
}
else
{
_repository = entityList.ToList();
}
}
IList<T> _repository;
#region IObjectSet<T> Members
public void AddObject(T entity)
{
_repository.Add(entity);
}
public void Attach(T entity)
{
this.AddObject(entity);
}
public void DeleteObject(T entity)
{
_repository.Remove(entity);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return _repository.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _repository.GetEnumerator();
}
#endregion
#region IQueryable Members
public Type ElementType
{
get { return typeof(T); }
}
public System.Linq.Expressions.Expression Expression
{
get { return _repository.AsQueryable<T>().Expression; }
}
public IQueryProvider Provider
{
get { return _repository.AsQueryable<T>().Provider; }
}
#endregion
}
It is important to note here that the TestHelper.CreateCustomerList() function has several overrides and returns a List<Customer> filled with dummy data.
After playing around a bit I realized that I could actually just create an Extension Method that would create an instance of the mock CustomerSet and therefore I could call it from within my Unit Test code. The Extension Method looks like this:
public static class ObjectSetExtension
{
public static IObjectSet<T> AsObjectSet<T>(this List<T> entities) where T : class
{
return new MockObjectSet<T>(entities);
}
}
Now if we revisit the unit test code, we get the following:
[TestMethod]
public void TestLoadValidCustomerContactNameWithSurname()
{
// Arrange
// Create the stub instance
INorthwindContext context = MockRepository.GenerateStub<INorthwindContext>();
// Create the dummy data
const string customerId = "555";
const string contactName = "James Person";
IObjectSet<Customer> customers = TestHelper.CreateCustomerList(contactName, customerId).AsObjectSet();
// declare the dummy instance we are going to use
Customer loadedCustomer;
// Explicitly state how the stubs should behave
context.Stub(stub => stub.Customers).Return(customers);
// Create a real instance of the CustomerManager that we want to put under test
Managers.CustomerManager manager = new Managers.CustomerManager(context);
// Act
manager.Load(customerId, out loadedCustomer);
// Assert
context.AssertWasCalled(stub => { var temp = stub.Customers; });
// Check the expected nature of the dummy intance
Assert.IsNotNull(loadedCustomer);
Assert.IsNotNull(loadedCustomer.ContactName);
Assert.IsTrue(loadedCustomer.ContactName == "James PERSON");
}
If we compare the two managers again (the manager that I had created in a previous blog posting depended on EF 1.0), we will see that the EF 2.0 instance actually contains lambda expressions to do the queries.
EF 1.0:
/// <summary>
/// Loads a customer instance with the relevant information from the database.
/// </summary>
/// <param name="i_customerId">The customerId of the customer data to be retrieved.</param>
/// <param name="o_customer">The customer instance to be created.</param>
public void Load(string i_customerId, out Customer o_customer)
{
if (string.IsNullOrEmpty(i_customerId))
{
throw new ArgumentException("Parameter cannot be null.", "i_customerId");
}
int numericVal;
if (!int.TryParse(i_customerId, out numericVal))
{
throw new ArgumentException("Parameter cannot be non-numeric.", "i_customerId");
}
if (numericVal < 0 || numericVal > 9999)
{
throw new ArgumentOutOfRangeException("i_customerId");
}
m_customerRepository.Load(i_customerId, out o_customer);
if (o_customer != null)
{
if (!string.IsNullOrEmpty(o_customer.ContactName) && o_customer.ContactName.Contains(" "))
{
o_customer.ContactName = o_customer.ContactName.Trim(' ');
string[] names = o_customer.ContactName.Split(' ');
if (names.Length > 1)
{
names[names.Length - 1] = names[names.Length - 1].ToUpper();
}
o_customer.ContactName = string.Join(" ", names);
}
}
}
EF 2.0:
/// <summary>
/// Loads a customer instance with the relevant information from the database.
/// </summary>
/// <param name="i_customerId">The customerId of the customer data to be retrieved.</param>
/// <param name="o_customer">The customer instance to be created.</param>
public void Load(string i_customerId, out Customer o_customer)
{
if (string.IsNullOrEmpty(i_customerId))
{
throw new ArgumentException("Parameter cannot be null.", "i_customerId");
}
int numericVal;
if (!int.TryParse(i_customerId, out numericVal))
{
throw new ArgumentException("Parameter cannot be non-numeric.", "i_customerId");
}
if (numericVal < 0 || numericVal > 9999)
{
throw new ArgumentOutOfRangeException("i_customerId");
}
var customers = (from cus in _context.Customers
where cus.CustomerID == i_customerId
select cus);
if (customers.Count() == 1)
{
o_customer = customers.Single<Customer>();
}
else
{
o_customer = null;
}
if (o_customer != null)
{
if (!string.IsNullOrEmpty(o_customer.ContactName) && o_customer.ContactName.Contains(" "))
{
o_customer.ContactName = o_customer.ContactName.Trim(' ');
string[] names = o_customer.ContactName.Split(' ');
if (names.Length > 1)
{
names[names.Length - 1] = names[names.Length - 1].ToUpper();
}
o_customer.ContactName = string.Join(" ", names);
}
}
}
In the second code, snippet, because I am calling straight to an instance of IObjectSet<Customer>, it could either be my mocked one or the actual Entity Framework instance, which looks like this (thanks to POCO binding):
public class NorthwindContext : ObjectContext, INorthwindContext
{
public NorthwindContext()
: base("name=NorthwindEntities", "NorthwindEntities")
{
}
private ObjectSet<Order> _orders;
private ObjectSet<Employee> _employees;
private ObjectSet<Customer> _customers;
#region INorthwindContext Members
IObjectSet<Employee> INorthwindContext.Employees
{
get { return _employees ?? (_employees = CreateObjectSet<Employee>()); }
}
IObjectSet<Customer> INorthwindContext.Customers
{
get { return _customers ?? (_customers = CreateObjectSet<Customer>()); }
}
IObjectSet<Order> INorthwindContext.Orders
{
get { return _orders ?? (_orders = CreateObjectSet<Order>()); }
}
#endregion
}
This means that when I run my code coverage for the EF 2.0 version, I will be hitting the true boundary between the entity and the model, thanks to a combination of POCO and the separation of concerns brought about by IObjectSet.
Ok, so there’s no kitchen sink – what would you do with it if there was?