Sunday, September 12, 2010

A Repository-like façade for Entity Framework – Part II

This is the second post in this series.

The first thing we need to do to enable using our POCOs with EF 4.0 is to decide how we want to handle change tracking and lazy loading of our child collections.

For now, let’s take full advantage of EF’s capabilities and allow it to generate the proxies for lazy loading and full change tracking.

To do this, there are four requirements:

  1. Each POCO must have an empty default constructor.
  2. Each property of the POCO must be declared virtual.
  3. The child collection properties must be of type ICollection<T>.
  4. New POCO objects must be instantiated with ObjectContext.CreateObject<T>.

Since List<T> is an implementation of ICollection<T>, we can keep it for our collection, but we need to mark all the properties as virtual. Here are our entities:

using System.Collections.Generic;

namespace Entities.School
{
public class Department
{
public virtual long Id { get; set; }

public virtual string Name { get; set; }

public virtual List<Course> Courses { get; set; }
}

public class Course
{
public virtual long Id { get; set; }

public virtual string Name { get; set; }

public virtual Department Department { set; get; }
}
}



Note that I have removed the code that kept the Department.Courses collection in sync, when the Course.Department is set. I had hoped that the proxy behavior would replace this “default” behavior, but it looks like from my testing that the proxy synchronization takes place, followed by the “base” behavior. This resulted in two entries being made in the Courses collection each time a Course was assigned to the Department. I removed the base behavior, which means that if the in-memory repository is used, the client code will need to synchronize the collection.







Next, we create the model. Right-click on the model canvas and select Generate Database from Model. This will create the DDL script to create the tables.



2010-09-12-1



Next, blank out the Custom Tool property on the file properties for the EDMX file. This way, no code will be generated for your model. This means we need to write our own ObjectContext class. Many authors recommend creating strongly-type properties for exposing each ObjectSet (Departments, Courses). You can do that, but it isn’t very useful for our Repository interface. Rather, we will create Query<T> and SetOf<T> methods to return the appropriately typed IQueryable and ObjectSet, respectively.





using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;

namespace Model
{
public class PocoContext : ObjectContext
{
public PocoContext(string connectString)
:
base(connectString)
{
ContextOptions.LazyLoadingEnabled
= true;
}

Dictionary
<Type, ObjectQuery> roots = new Dictionary<Type, ObjectQuery>();

public ObjectSet<T> SetOf<T>() where T : class
{
ObjectQuery root;
if (!roots.TryGetValue(typeof(T), out root))
{
var
set = CreateObjectSet<T>();
roots[
typeof(T)] = set;
return set;
}

return (ObjectSet<T>)root;
}

public IQueryable<T> Query<T>() where T : class
{
return (IQueryable<T>)SetOf<T>();
}
}
}







Finally, we create our Repository implementation.



using System.Linq;

namespace Model.School.Model2
{
using Interfaces;

public class SchoolRespository : BaseRepository
{
private PocoContext context;

public SchoolRespository(string connectString = "name=Model2Container")
{
context
= new PocoContext(connectString);
}

public override IQueryable<T> Query<T>()
{
return context.Query<T>();
}

public override T New<T>()
{
return context.CreateObject<T>();
}

public override void SetNext<T>(T obj)
{
// no need for this with SQL Server auto-numbering
}

public override void Add<T>(T obj)
{
context.SetOf
<T>().AddObject(obj);
}

public override void Remove<T>(T obj)
{
context.SetOf
<T>().DeleteObject(obj);
}

public override void Commit()
{
context.SaveChanges();
}
}
}





Here is a test that shows how to use the SchoolRepository that we have created.



[TestMethod]
public void Test_SchoolRepository()
{
IRepository repo
= new SchoolRespository();

var count
= repo.Query<Department>().Count();

try
{
var math
= repo.CreateNext<Department>(d =>
{
d.Name
= "Mathematics";
});

var algebra
= repo.CreateNext<Course>(c =>
{
c.Name
= "Algebra";
c.Department
= math;
});

repo.Commit();

repo
= new SchoolRespository();
var course
= (from c in repo.Query<Course>()
where c.Name == "Algebra"
select c).First();
Assert.IsNotNull(course);
Assert.IsTrue(course.Department.Id
== math.Id);

var dept
= (from d in repo.Query<Department>()
where d.Name == "Mathematics"
select d).First();
Assert.IsTrue(dept.Courses.Count
== 1);
Assert.IsTrue(dept.Courses[
0] == course);

}
finally
{
repo
= new SchoolRespository();

// remove course and dept

var course
= repo.Query<Course>()
.Where(o
=> o.Name == "Algebra").FirstOrDefault();
if (course != null) repo.Remove<Course>(course);

var dept
= repo.Query<Department>()
.Where(o
=> o.Name == "Mathematics").FirstOrDefault();
if (dept != null) repo.Remove<Department>(dept);
repo.Commit();
}
}

No comments: