Wednesday, February 03, 2010

Mocking Entity Framework ObjectSets

After finally getting my head around object-graph deletes in Entity Framework v4, I set about getting some better testing around my EF code.  The first step was updating my self-tracking entities template to have my object-sets be exposed as IObjectSets which involved a small tweak to my TT file.

I ran into some challenges using a Rhino Mock of IObjectSet, so I let Google do some work for me and I landed on Jamie Phillip’s post about mocking IObjectSet via his MockObjectSet., which was exactly what I was looking for.  Adding this class into my test library got me 90% of where I wanted to be, but I still had compile errors on the spots where I was building out my object-graphs with “.Include(“”)” calls. 

After some time digging through the EF4 code with Reflector,  I was not finding what I thought I needed, an IObjectQuery interface, which would allow me to mock the Include method.  Again, I went back to Google and ran across a MSDN Forum post which Diego Vega answered with an almost too obvious solution to my exact problem: use an extension-method for Include.

Since I was only interested in mocking out the “Include” behavior, and have my MockOjectSet return my configured entity list, I went with Diego’s first suggestion for Include, which looks like this:

public static IQueryable<T> Include<T>(this IQueryable<T> source, string path){
   var objectQuery = source as ObjectQuery<T>;
   if (objectQuery != null){
      return objectQuery.Include(path);
   }
   return source;
}

The only pain point to this approach to testing, is that I’m stuck remembering that I need to add a new extension method whenever I utilize one of the methods in the ObjectQuery<T> class – Union, Distinct, etc.

Sunday, January 31, 2010

Using log4net with .Net 4.0 WPF Applications

I ran into some issues trying to get log4net up and running on my current project, which is being written in VS2010 Beta2, targeting the .net 4.0 framework.  I went about adding a reference to log4net, setup my app.config with a rolling log-file appender and added a call to XmlConfigurator.Configure() in one of my classes.

I then went to compile and got the following error: The type or namespace name 'log4net' could not be found (are you missing a using directive or an assembly reference?) 

I double-checked, and yes, I had indeed added a reference to log4net, so what was wrong?  Turns out, for .net 4.0 WPF projects, the default Target Framework is the “.Net Framework 4 Client Profile”, which is a subset of the full framework with the goal of shrinking footprint of the .net framework needed to download for a typical windows application.  However, log4net apparently needs access to system.web.dll, which isn’t included in the client profile. 

To fix your app so it’ll compile/run, open your project’s properties page, and set the Target Framework to “.Net Framework 4” and save your changes. 

Saturday, January 30, 2010

Entity Framework v4 Object-Graph Deleting Quirks

Update: Danny was quick to respond with a great explanation about how EF4 deals with object-graphs and deletes.

I’ve been getting up to speed on Entity Framework V4 (EF4) lately, for use in a WPF application that I’m working on.   One of the issues I’m coming up against is removal of objects in one-to-many and many-to-many relationships.  It’s still not clear to me why things work the way they do in EF4, but I’ll lay out a few scenarios and talk about what works and what doesn’t work and hope that someone can later comment as to why things may not be working as I would expect.

Scenario 1: A many-to-many relationship where the join table has its own primary key (an identity column in this case):

image

Given the following code, I’d expect to be able to remove an object from the Employee.ProjectAssignments collection and see that object removed from the database when I call SaveChanges on my ObjectContext:

using (var context = new EFTestDBEntities()){
                var employeeGraph = (from e in context.Employees.Include("ProjectAssignments").Include("ProjectAssignments.Project") where e.ID == employeeID select e).FirstOrDefault();
                if (employeeGraph != null){
                    var assignment = employeeGraph.ProjectAssignments.First();
                    employeeGraph.ProjectAssignments.Remove(assignment);

                    context.SaveChanges();
                }
            }

When I hit the “SaveChanges()” call, I get the following exception:

System.InvalidOperationException: The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

This strikes me as strange – I assume that I’ve indicated that the employee is no longer related to this project-assignment, so the object is essentially “worthless” and I’d expect it to be removed.

In order to successfully remove this project-assignment, I had to do the following:

using (var context = new EFTestDBEntities()){
                var employeeGraph = (from e in context.Employees.Include("ProjectAssignments").Include("ProjectAssignments.Project") where e.ID == employeeID select e).FirstOrDefault();
                if (employeeGraph != null){
                    ProjectAssignment assignment = employeeGraph.ProjectAssignments.First();
                    context.ProjectAssignments.DeleteObject(assignment);

                    context.SaveChanges();
                    Assert.AreEqual(0, employeeGraph.ProjectAssignments.Count);
                }
            }

Okay, so if I “manually” delete the object by the explicit call to DeleteObject, things work.  So far so good, I can work with this.

Scenario 2: A many-to-many relationship where the join table has a primary key that is composed by foreign-keys to the tables on either side of the relationship:

image 

In this case, the code I’d expect to work, actually worked and allowed me to delete a class-schedule from a teacher:

using (var context = new EFTestDBEntities()){
                var teacherGraph = (from t in context.Teachers.Include("ClassSchedules").Include("ClassSchedules.Class") where t.ID == teacherID select t).FirstOrDefault();
                if(teacherGraph != null){
                    var classSchedule = teacherGraph.ClassSchedules.First();
                    teacherGraph.ClassSchedules.Remove(classSchedule);

                    context.SaveChanges();
                }
            }

Why does EF4 make a distinction in behavior between these two scenarios that are essentially the same operation yet one object-graph allows me to write what I naturally want to write (scenario 2), but not the other?

Scenario 3: A one-to-many relationship:

image

Once again, I tried to write the code in a way I’d expect to naturally work:

using (var context = new EFTestDBEntities()){
                var orderGraph = (from o in context.Orders.Include("OrderDetails") where o.ID == orderID select o).FirstOrDefault();
                if(orderGraph != null){
                    var detail = orderGraph.OrderDetails.First();
                    orderGraph.OrderDetails.Remove(detail);

                    context.SaveChanges();
                }
            }

And once again, I got the following exception:

System.InvalidOperationException: The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

As with scenario 1, I had to write the following code to successfully remove the OrderDetail record:

using (var context = new EFTestDBEntities()){
                var orderGraph = (from o in context.Orders.Include("OrderDetails") where o.ID == orderID select o).FirstOrDefault();
                if (orderGraph != null){
                    var detail = orderGraph.OrderDetails.First();
                    context.OrderDetails.DeleteObject(detail);

                    context.SaveChanges();
                }
            }

Alright, so I have managed to work around some of the quirks with EF4, but I really don’t understand why removal of objects in the object-graph don’t cause a proper remove/delete in some cases, yet work just as I expect in others.