Objects should not know how to save themselves, load themselves, or filter themselves. That’s a familiar mantra in software development, and especially in Domain Driven Development. There is a good bit of wisdom in this mantra. Having persistence knowledge bound too tightly to domain objects complicates testing, refactoring, and reuse. In ObjectContext, the classes generated by Entity Framework for model entities are heavily dependent on the plumbing of Entity Framework. For some developers, these classes know too much about the persistence mechanism, and they are too closely tied to the concerns of models and mapping. There is another option, however.
Entity Framework also supports using your own classes for the entities in the model. The term Plain Old CLR Object, often simply referred to as POCO, isn’t meant to imply that your classes are either plain or old. It merely means that they don’t contain any reference at all to specialized frameworks, they don’t need to derive from third-party code, they don’t need to implement any special interface, and they don’t need to live in any special assembly or namespace. You may implement your domain objects however you see fit and tie them to the model with a custom object context. With that being said, you are ready to leverage all of the power of Entity Framework and follow just about any architectural pattern you choose. You can also use DbContext to generate the POCO classes for you.
This chapter covers a wide variety of recipes specific to POCO. The first recipe shows you the basics of using POCO. The remaining recipes focus on loading entities and keeping Entity Framework in sync with the state of your objects.
In this chapter, we’ve intentionally focused on writing most of the POCO-related code by hand to demonstrate how things work. All of the work involved in building the POCO plumbing goes away if you use the POCO T4 template available from the ADO.NET development team at Microsoft.
8-1. Using POCO
Solution
Let’s say that you have a data model like the one shown in Figure 8-1.
Figure 8-1. A database model for customers and their orders
To create an Entity Framework model based on the database tables in Figure 8-1, and using the POCO classes generated by Entity Framework representing an Order, OrderDetail, Customer, and Product, follow the steps below:
Figure 8-2. The model for our customers’ orders
Listing 8-1. The Plain Old CLR classes for Our Model
public partial class Customer
{
public Customer()
{
this.Orders = new HashSet<Order>();
}
public int CustomerId { get; set; }
public string ContactName { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public partial class Order
{
public Order()
{
this.OrderDetails = new HashSet<OrderDetail>();
}
public int OrderId { get; set; }
public int CustomerId { get; set; }
public System.DateTime OrderDate { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
}
public partial class OrderDetail
{
public int OrderId { get; set; }
public int ProductId { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public virtual Order Order { get; set; }
public virtual Product Product { get; set; }
}
public partial class Product
{
public Product()
{
this.OrderDetails = new HashSet<OrderDetail>();
}
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
}
Notice that there is no association from Product to OrderDetail, because we removed that navigation property in the designer.
Listing 8-2. DbContext for Our Model Created While Generating an Entity Data Model
public partial class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("name=EFRecipesEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderDetail> OrderDetails { get; set; }
public DbSet<Product> Products { get; set; }
}
This completes the model with the generated POCO classes. The code in Listing 8-3 demonstrates inserting into and querying our model.
Listing 8-3. Using Our POCO Classes
using (var context = new EFRecipesEntities())
{
var tea = new Product { ProductName = "Green Tea", UnitPrice = 1.09M };
var coffee = new Product
{
ProductName = "Colombian Coffee",
UnitPrice = 2.15M
};
var customer = new Customer { ContactName = "Karen Marlowe" };
var order1 = new Order { OrderDate = DateTime.Parse("10/06/13") };
order1.OrderDetails.Add(new OrderDetail
{
Product = tea,
Quantity = 4,
UnitPrice = 1.00M
});
order1.OrderDetails.Add(new OrderDetail
{
Product = coffee,
Quantity = 3,
UnitPrice = 2.15M
});
customer.Orders.Add(order1);
context.Customers.Add(customer);
context.SaveChanges();
}
using (var context = new EFRecipesEntities())
{
var query = context.Customers.Include("Orders.OrderDetails.Product");
foreach (var customer in query)
{
Console.WriteLine("Orders for {0}", customer.ContactName);
foreach (var order in customer.Orders)
{
Console.WriteLine("--Order Date: {0}--",
order.OrderDate.ToShortDateString());
foreach (var detail in order.OrderDetails)
{
Console.WriteLine(
" {0}, {1} units at {2} each, unit discount: {3}",
detail.Product.ProductName,
detail.Quantity.ToString(),
detail.UnitPrice.ToString("C"),
(detail.Product.UnitPrice - detail.UnitPrice).ToString("C"));
}
}
}
}
The following is the output of the code in Listing 8-3:
Orders for Karen Marlowe
--Order Date: 4/19/2010--
Green Tea, 4 units at $1.00 each, unit discount: $0.09
Colombian Coffee, 3 units at $2.15 each, unit discount: $0.00
How It Works
The POCO class generation is the default feature of current version of Entity Framework. Code generation property value is already set to None. The DbContext class is also generated separately, so no data access code is plugged into the POCO classes.
All of the classes corresponding to each of the entities in our model are created. They are pretty simple and clean. Of course, without code generation, no DbContext is generated. To implement a DbContext that is specific to our model and our entities, a new class derived from DbContext is created while generating the Entity Data Model, and this class provides properties of type DbSet<T> corresponding to each of the Db sets in our context. By default, our EFRecipesEntities DbContext has the constructor code that enables it to be connected to the underlying database.
8-2. Loading Related Entities with POCO
Solution
Suppose that you have a model like the one in Figure 8-3.
Figure 8-3. A model representing venues, their events, and the competitors in the events
We’re using POCO for our entities, and we want to eagerly load the related entities (navigation properties). To do this, we use the Include() method available on the object context. The code in Listing 8-4 illustrates using the Include() method to do this.
Listing 8-4. Using the Include() Method Explicitly to Load Navigation Properties
class Program
{
static void Main(string[] args)
{
RunExample();
}
static void RunExample()
{
using (var context = new EFRecipesEntities())
{
var venue = new Venue { Name = "Sports and Recreational Grounds" };
var event1 = new Event { Name = "Inter-school Soccer" };
event1.Competitors.Add(new Competitor { Name = "St. Mary's School" });
event1.Competitors.Add(new Competitor { Name = "City School" });
venue.Events.Add(event1);
context.Venues.Add(venue);
context.SaveChanges();
}
using (var context = new EFRecipesEntities())
{
foreach (var venue in context.Venues.Include("Events").Include("Events.Competitors"))
{
Console.WriteLine("Venue: {0}", venue.Name);
foreach (var evt in venue.Events)
{
Console.WriteLine(" Event: {0}", evt.Name);
Console.WriteLine(" --- Competitors ---");
foreach (var competitor in evt.Competitors)
{
Console.WriteLine(" {0}", competitor.Name);
}
}
}
}
}
}
public partial class Venue
{
public Venue()
{
this.Events = new HashSet<Event>();
}
public int VenueId { get; set; }
public string Name { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
public partial class Event
{
public Event()
{
this.Competitors = new HashSet<Competitor>();
}
public int EventId { get; set; }
public string Name { get; set; }
public int VenueId { get; set; }
public virtual ICollection<Competitor> Competitors { get; set; }
public virtual Venue Venue { get; set; }
}
public partial class Competitor
{
public int CompetitorId { get; set; }
public string Name { get; set; }
public int EventId { get; set; }
public virtual Event Event { get; set; }
}
public partial class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("name=EFRecipesEntities")
{
this.Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Competitor> Competitors { get; set; }
public DbSet<Event> Events { get; set; }
public DbSet<Venue> Venues { get; set; }
}
The following is the output of the code in Listing 8-4:
Venue: City Center Hall
Event: All Star Boxing
--- Competitors ---
Big Joe Green
Terminator Tim
Venue: Sports and Recreational Grounds
Event: Inter-school Soccer
--- Competitors ---
St. Mary's School
City School
How It Works
When we’re using code generated by Entity Framework for our model, we use the Include() method on the context query objects to load the related entities, and these related entities can be lists of entities or single objects. There are three methods of loading or querying related entities in Entity Framework: Eager Loading, Lazy Loading, and Explicit Loading. We have used the Include() method to demonstrate eagerly loading related entities. By default, Lazy Loading is enabled in Entity Framework, but we have disabled that here. To load a navigation property explicitly when using POCO, you need to use the Include() method exposed on the DbContext.
8-3. Lazy Loading with POCO
Solution
Let’s say that you have a model like the one in Figure 8-4.
Figure 8-4. A simple model for traffic tickets, the offending vehicles, and the details of the violation
To enable lazy loading, you don’t need to do anything. Lazy loading is enabled by default when an Entity Data Model is added into a Visual Studio project. The code in Listing 8-5 illustrates this approach.
Listing 8-5. Entity Classes Generation and Properties Set to Virtual: A Default Behavior of Entity Framework
class Program
{
static void Main(string[] args)
{
RunExample();
}
static void RunExample()
{
using (var context = new EFRecipesEntities())
{
var vh1 = new Vehicle { LicenseNo = "BR-549" };
var t1 = new Ticket { IssueDate = DateTime.Parse("06/10/13") };
var v1 = new Violation
{
Description = "20 MPH over the speed limit",
Amount = 125M
};
var v2 = new Violation
{
Description = "Broken tail light",
Amount = 50M
};
t1.Violations.Add(v1);
t1.Violations.Add(v2);
t1.Vehicle = vh1;
context.Tickets.Add(t1);
var vh2 = new Vehicle { LicenseNo = "XJY-902" };
var t2 = new Ticket { IssueDate = DateTime.Parse("06/12/13") };
var v3 = new Violation
{
Description = "Parking in a no parking zone",
Amount = 35M
};
t2.Violations.Add(v3);
t2.Vehicle = vh2;
context.Tickets.Add(t2);
context.SaveChanges();
}
using (var context = new EFRecipesEntities())
{
foreach (var ticket in context.Tickets)
{
Console.WriteLine(" Ticket: {0}, Total Cost: {1}",
ticket.TicketId.ToString(),
ticket.Violations.Sum(v => v.Amount).ToString("C"));
foreach (var violation in ticket.Violations)
{
Console.WriteLine(" {0}", violation.Description);
}
}
}
}
}
public partial class Ticket
{
public Ticket()
{
this.Violations = new HashSet<Violation>();
}
public int TicketId { get; set; }
public int VehicleId { get; set; }
public System.DateTime IssueDate { get; set; }
public virtual Vehicle Vehicle { get; set; }
public virtual ICollection<Violation> Violations { get; set; }
}
public partial class Vehicle
{
public Vehicle()
{
this.Tickets = new HashSet<Ticket>();
}
public int VehicleId { get; set; }
public string LicenseNo { get; set; }
public virtual ICollection<Ticket> Tickets { get; set; }
}
public partial class Violation
{
public int ViolationId { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
public int TicketId { get; set; }
public virtual Ticket Ticket { get; set; }
}
public partial class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("name=EFRecipesEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Ticket> Tickets { get; set; }
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<Violation> Violations { get; set; }
The following is the output of the code in Listing 8-5:
Ticket: 1, Total Cost: $175.00
20 MPH over the speed limit
Broken tail light
Ticket: 2, Total Cost: $35.00
Parking in a no parking zone
How It Works
Lazy loading is the default setting when generating an Entity Data Model. The navigation entity properties are also marked as virtual by default. You don’t need to do anything explicitly to get this to work.
We have not done anything in the console program code to load the Violation object, which is related to the Ticket object when the ticket context is fetched. Lazy loading enables the access of related entity properties at the moment you access them in your code. It does not require you to query those properties at the time of the first loading of the context object of the main entity, as we did using Include() method in the previous recipe.
8-4. POCO with Complex Type Properties
Solution
Suppose that your model looks like the one in Figure 8-5. In this model, the Name property is a complex type.
Figure 8-5. A model for an employee. The Name property is a complex type, composed of FirstName and LastName
Complex types are supported with POCO. When we refactor two or more entity properties to a new complex type, a new class is generated by default for that complex type. A property of the complex type class is also added into the main entity POCO class. Only classes are supported, as Entity Framework generates these while saving new complex types. The code in Listing 8-6 illustrates using the Name class for the complex type representing the employee’s FirstName and LastName.
Listing 8-6. Using a Complex Type with POCO
class Program
{
static void Main(string[] args)
{
RunExample();
}
static void RunExample()
{
using (var context = new EFRecipesEntities())
{
context.Employees.Add(new Employee
{
Name = new Name
{
FirstName = "Annie",
LastName = "Oakley"
},
Email = "[email protected]"
});
context.Employees.Add(new Employee
{
Name = new Name
{
FirstName = "Bill",
LastName = "Jordan"
},
Email = "[email protected]"
});
context.SaveChanges();
}
using (var context = new EFRecipesEntities())
{
foreach (var employee in
context.Employees.OrderBy(e => e.Name.LastName))
{
Console.WriteLine("{0}, {1} email: {2}",
employee.Name.LastName,
employee.Name.FirstName,
employee.Email);
}
}
}}
public partial class Employee
{
public Employee()
{
this.Name = new Name();
}
public int EmployeeId { get; set; }
public string Email { get; set; }
public Name Name { get; set; }
}
public partial class Name
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public partial class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("name=EFRecipesEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Employee> Employees { get; set; }
}
The following is the output of the code in Listing 8-6:
Jordan, Bill email:[email protected]
Oakley, Annie email:[email protected]
How It Works
When you use complex types with POCO, keep in mind the following two rules:
In Entity Framework, complex types do not leverage change tracking. Changes to complex types will not be reflected in change tracking. This means that if you mark the properties on a complex type as virtual, there is no change-tracking proxy support. All change tracking is snapshot-based.
When you delete or update a POCO entity with a complex type without first loading it from the database, you need to be careful to create an instance of the complex type. In Entity Framework, instances of complex types are structurally part of the entity, and null values are not supported. The code in Listing 8-7 illustrates one way to handle deletes.
Listing 8-7. Deleting a POCO Entity with a Complex Type
int id = 0;
using (var context = new EFRecipesEntities())
{
var emp = context.Employees.Where(e =>
e.Name.FirstName.StartsWith("Bill")).FirstOrDefault();
id = emp.EmployeeId;
}
using (var context = new EFRecipesEntities())
{
var empDelete = new Employee
{
EmployeeId = id,
};
context.Employees.Attach(empDelete);
context.Employees.Remove(empDelete);
context.SaveChanges();
}
In Listing 8-7, we first have to find the EmployeeId of Bill Jordan. Because we are trying to show how we would delete Bill without first loading the entity into the context, we create a new context to illustrate deleting Bill given just his EmployeeId. We need to create an instance of the Employee entity complete with the Name type. Because we are deleting, it doesn’t matter much what values we put in for FirstName and LastName. The key is that the Name property is not null. We satisfy this requirement by assigning a new (dummy) instance of Name. We then Attach() the entity and call Remove() and SaveChanges(). This deletes the entity.
8-5. Notifying Entity Framework About Object Changes
Problem
You are using POCO, and you want to have Entity Framework and the object state manager notified of changes to your objects.
Solution
Let’s say that you have a model like the one in Figure 8-6.
Figure 8-6. A model for donors and their donations
This model represents donations and donors. Because some donations are anonymous, the relationship between donor and donation is 0..1 to *.
We want to make changes to our entities, such as moving a donation from one donor to another, and have Entity Framework and the object state manager notified of these changes. In addition, we want Entity Framework to leverage this notification to fix up any relationships that are affected by such changes. In our case, if we change the Donor on a Donation, we want Entity Framework to fix up both sides of the relationship. The code in Listing 8-8 demonstrates how to do this.
The key part of Listing 8-8 is that we marked each property as virtual and each collection a type of ICollection<T>. This allows Entity Framework to create proxies for our POCO entities that enable change tracking. When creating instances of POCO entity types, Entity Framework often creates instances of a dynamically generated derived type that acts as a proxy for the entity. This proxy overrides some virtual properties of the entity that inserts hooks for performing actions automatically when the property is accessed. This mechanism is used to support lazy loading of relationships and change tracking of objects. Note that Entity Framework will not create proxies for types where there is nothing for the proxy to do. This means that you can also avoid proxies by having types that are sealed and/or have no virtual properties.
Listing 8-8. By Marking Each Property as virtual and Each Collection a Type of ICollection<T>, We Get Proxies That Enable Change Tracking
class Program
{
static void Main(string[] args)
{
RunExample();
}
static void RunExample()
{
using (var context = new EFRecipesEntities())
{
var donation = context.Donations.Create();
donation.Amount = 5000M;
var donor1 = context.Donors.Create();
donor1.Name = "Jill Rosenberg";
var donor2 = context.Donors.Create();
donor2.Name = "Robert Hewitt";
// give Jill the credit for the donation and save
donor1.Donations.Add(donation);
context.Donors.Add(donor1);
context.Donors.Add(donor2);
context.SaveChanges();
// now give Robert the credit
donation.Donor = donor2;
// report
foreach (var donor in context.Donors)
{
Console.WriteLine("{0} has given {1} donation(s)", donor.Name,
donor.Donations.Count().ToString());
}
Console.WriteLine("Original Donor Id: {0}",
context.Entry(donation).OriginalValues["DonorId"]);
Console.WriteLine("Current Donor Id: {0}",
context.Entry(donation).CurrentValues["DonorId"]);
}
}}
public partial class Donor
{
public Donor()
{
this.Donations = new HashSet<Donation>();
}
public virtual int DonorId { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Donation> Donations { get; set; }
}
public partial class Donation
{
public virtual int DonationId { get; set; }
public virtual Nullable<int> DonorId { get; set; }
public virtual decimal Amount { get; set; }
public virtual Donor Donor { get; set; }
}
public partial class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("name=EFRecipesEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Donation> Donations { get; set; }
public DbSet<Donor> Donors { get; set; }
}
The following is the output of the code in Listing 8-8:
Jill Rosenberg has given 0 donation(s)
Robert Hewitt has given 1 donation(s)
Original Donor Id: 1
Current Donor Id: 2
How It Works
By default, Entity Framework uses a snapshot-based approach for detecting changes made to POCO entities. If you make some minor code changes to your POCO entities, Entity Framework can create change-tracking proxies that keep the DbContext synchronized with the runtime changes in your POCO entities.
There are two important benefits that come with change-tracking proxies. First, the DbContext stays informed of the changes, and it can keep the entity object graph state information synchronized with your POCO entities. This means that no time need be spent detecting changes using the snapshot-based approach.
Additionally, when the DbContext is notified of changes on one side of a relationship, it can mirror the change on the other side of the relationship if necessary. In Listing 8-8, when we moved a Donation from one Donor to another, Entity Framework also fixed up the Donations collections of both Donors.
For the Entity Framework to create the change-tracking proxies for your POCO classes, the following conditions must be met.
Once your POCO classes have met these requirements, Entity Framework will return instances of the proxies for your POCO classes. If you need to create instances, as we have in Listing 8-8, you will need to use the Create() method on the DbContext. This method creates the instance of the proxy for your POCO entity, and it initializes all of the collections as instances of EntityCollection. It is this initialization of your POCO class’s collections as instances of EntityCollection that enables fixing up relationships.
8-6. Retrieving the Original (POCO) Object
Solution
Let’s say that you are using a model like the one in Figure 8-7, and you are working in a disconnected scenario. You want to use a Where clause with FirstOrDefault() to retrieve the original object from the database before you apply changes received from a client.
Figure 8-7. A model with a single Item entity
To update the entity with new values after retrieving the entity and then to apply changes to save in database, follow the pattern in Listing 8-9.
Listing 8-9. Retrieving the Newly Added Entity and Replacing Its Values Using the Entry() Method
class Program
{
static void Main(string[] args)
{
RunExample();
}
static void RunExample()
{
int itemId = 0;
using (var context = new EFRecipesEntities())
{
var item = new Item
{
Name = "Xcel Camping Tent",
UnitPrice = 99.95M
};
context.Items.Add(item);
context.SaveChanges();
// keep the item id for the next step
itemId = item.ItemId;
Console.WriteLine("Item: {0}, UnitPrice: {1}",
item.Name, item.UnitPrice.ToString("C"));
}
using (var context = new EFRecipesEntities())
{
// pretend this is the updated
// item we received with the new price
var item = new Item
{
ItemId = itemId,
Name = "Xcel Camping Tent",
UnitPrice = 129.95M
};
var originalItem = context.Items.Where(x => x.ItemId == itemId).FirstOrDefault<Item>();
context.Entry(originalItem).CurrentValues.SetValues(item);
context.SaveChanges();
}
using (var context = new EFRecipesEntities())
{
var item = context.Items.Single();
Console.WriteLine("Item: {0}, UnitPrice: {1}", item.Name,
item.UnitPrice.ToString("C"));
}
}
}
public partial class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public decimal UnitPrice { get; set; }
}
public partial class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("name=EFRecipesEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Item> Items { get; set; }
}
The following is the output of the code in Listing 8-9:
Item: Xcel Camping Tent, UnitPrice: $99.95
Item: Xcel Camping Tent, UnitPrice: $129.95
How It Works
In Listing 8-9, we inserted an item into the model and saved it to the database. Then we pretended to receive an updated item, perhaps from a Silverlight client.
Next we need to update the item in the database. To do this, we need to get the entity from the database into the context. To get the entity, we used a Where clause with FirstorDefault and checked with the ID of the item. After that, we used the Entry() method of the context, which enables access to entire entity to apply any methods on that entity. Thus we used CurrentValues.SetValues to replace the original values with new values that come through the client. Finally, SaveChanges is called on the DbContext.
8-7. Manually Synchronizing the Object Graph and the Change Tracker
Problem
You want to control manually the synchronization between your POCO classes and the Change Tracker.
Change Tracker has access to the information that Entity Framework is storing about the entities that it is tracking. This information goes beyond the values stored in the properties of your entities and includes the current state of the entity, the original values from the database, which properties have been modified, and other data. The Change Tracker also gives access to additional operations that can be performed on an entity, such as reloading its values from the database to ensure that you have the latest data.
There are two different ways that Entity Framework can track changes to your objects: snapshot change tracking and change-tracking proxies.
Snapshot Change Tracking
POCO classes don’t contain any logic to notify Entity Framework when a property value is changed. Because there is no way to be notified when a property value changes, Entity Framework will take a snapshot of the values in each property when it first sees an object and store the values in memory. This snapshot occurs when the object is returned from a query or when we add it to a DbSet. When Entity Framework needs to know what changes have been made, it will scan each object and compare its current values to the snapshot. This process of scanning each object is triggered through a method of Change Tracker called DetectChanges.
Change-Tracking Proxies
The other mechanism for tracking changes is through change-tracking proxies, which allow Entity Framework to be notified of changes as they are made. Change-tracking proxies are created using the mechanism of dynamic proxies that are created for lazy loading, but in addition to providing for lazy loading, they also have the ability to communicate changes to the context. To use change-tracking proxies, you need to structure your classes in such a way that Entity Framework can create a dynamic type at runtime that derives from your POCO class and overrides every property. This dynamic type, known as a dynamic proxy, includes logic in the overridden properties to notify Entity Framework when those properties are changed.
Snapshot change tracking depends on Entity Framework being able to detect when changes occur. The default behavior of the DbContext API is to perform this detection automatically as the result of many events on the DbContext. DetectChanges not only updates the context’s state management information so that changes can be persisted to the database, but it also performs relationship fix-up when you have a combination of reference navigation properties, collection navigation properties, and foreign keys. It’s important to have a clear understanding of how and when changes are detected, what to expect from them, and how to control them.
The most obvious time that Entity Framework needs to know about changes is during SaveChanges, but there are many others. For example, if we ask the Change Tracker for the current state of an object, it will need to scan and check if anything has changed. Scanning isn’t just restricted to the object in question either—many of the operations you perform on the DbContext API will cause DetectChanges to be run. In most cases, DetectChanges is fast enough that it doesn’t cause performance issues. However, if you have a very large number of objects in memory or you are performing a lot of operations on DbContext in quick succession, the automatic DetectChanges behavior may be a performance concern. Fortunately, you have the option of switching off the automatic DetectChanges behavior and calling it manually when you know that it needs to be called. Failure to do this can result in unexpected side effects. DbContext takes care of this requirement for you, provided that you leave automatic DetectChanges enabled. If you switch it off, you are responsible for calling DetectChanges for poorly performing sections of code and to reenable it once the section in question has finished executing. Automatic DetectChanges can be toggled on and off via the DbContext.Configuration.AutoDetectChangesEnabled Boolean flag.
Solution
Suppose that we have a model for speakers and the talks prepared for various conferences. The model might look something like the one in Figure 8-8.
Figure 8-8. A model with a many-to-many association between speakers and the talks they prepare
The first thing to note in our model is that Speaker and Talk are in a many-to-many association. We have, through an independent association (and in an intermediate SpeakerTalk table in the database), a model that supports many speakers for any given talk and many talks for any given speaker.
We want to control manually the synchronization between our object graph and the Change Tracker. We will do this by calling the DetectChanges() method. Along the way, we’ll illustrate how the synchronization is progressing.
Follow the pattern in Listing 8-10 to synchronize manually your POCO object graph with the Change Tracker.
Listing 8-10. Using DetectChanges() Explicitly When Required to Synchronize the Change Tracker Manually
class Program
{
static void Main(string[] args)
{
RunExample();
}
static void RunExample()
{
using (var context = new EFRecipesEntities())
{
context.Configuration.AutoDetectChangesEnabled = false;
var speaker1 = new Speaker { Name = "Karen Stanfield" };
var talk1 = new Talk { Title = "Simulated Annealing in C#" };
speaker1.Talks = new List<Talk> { talk1 };
// associations not yet complete
Console.WriteLine("talk1.Speaker is null: {0}",
talk1.Speakers == null);
context.Speakers.Add(speaker1);
// now it's fixed up
Console.WriteLine("talk1.Speaker is null: {0}",
talk1.Speakers == null);
Console.WriteLine("Number of added entries tracked: {0}",
context.ChangeTracker.Entries().Where(e => e.State == System.Data.Entity.EntityState.Added).Count());
context.SaveChanges();
// change the talk's title
talk1.Title = "AI with C# in 3 Easy Steps";
Console.WriteLine("talk1's state is: {0}",
context.Entry(talk1).State);
context.ChangeTracker.DetectChanges();
Console.WriteLine("talk1's state is: {0}",
context.Entry(talk1).State);
context.SaveChanges();
}
using (var context = new EFRecipesEntities())
{
foreach (var speaker in context.Speakers.Include("Talks"))
{
Console.WriteLine("Speaker: {0}", speaker.Name);
foreach (var talk in speaker.Talks)
{
Console.WriteLine(" Talk Title: {0}", talk.Title);
}
}
}
}
}
public partial class Speaker
{
public int SpeakerId { get; set; }
public string Name { get; set; }
public ICollection<Talk> Talks { get; set; }
}
public partial class Talk
{
public int TalkId { get; set; }
public string Title { get; set; }
public System.DateTime CreateDate { get; set; }
public System.DateTime RevisedDate { get; set; }
public ICollection<Speaker> Speakers { get; set; }
}
public partial class EFRecipesEntities : DbContext
{
public EFRecipesEntities()
: base("name=EFRecipesEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Speaker> Speakers { get; set; }
public DbSet<Talk> Talks { get; set; }
public override int SaveChanges()
{
var changeSet = this.ChangeTracker.Entries().Where(e => e.Entity is Talk);
if (changeSet != null)
{
foreach (var entry in changeSet.Where(c => c.State == System.Data.Entity.EntityState.Added).Select(a => a.Entity as Talk))
{
entry.CreateDate = DateTime.UtcNow;
entry.RevisedDate = DateTime.UtcNow;
}
foreach (var entry in changeSet.Where(c => c.State == System.Data.Entity.EntityState.Modified).Select(a => a.Entity as Talk))
{
entry.RevisedDate = DateTime.UtcNow;
}
}
return base.SaveChanges();
}
}
The following is the output of the code in Listing 8-10:
talk1.Speaker is null: True
talk1.Speaker is null: False
Number of added entries tracked: 2
talk1's state is: Unchanged
talk1's state is: Modified
Speaker: Karen Stanfield
Talk Title: AI with C# in 3 Easy Steps
How It Works
The code in Listing 8-10 is a little involved, so let’s take it one step at a time. First off, we create a speaker and a talk. Then we add the talk to the speaker’s collection. At this point, the talk is part of the speaker’s collection, but the speaker is not part of the talk’s collection. The other side of the association has not been fixed up just yet.
Next we add the speaker to the DbContext with Add(speaker1). The second line of the output shows now that the talk’s speaker collection is correct. Entity Framework has fixed up the other side of the association. Here Entity Framework did two things. It notified the object state manager that there are three entries to be created, although it is not shown in the result of number of entities added by Entity Framework, as it considers many-to-many relationships as independent relationships and not as separate entities. Thus it is only showing the entries as two: one of these entries is for the speaker and the other is for the talk. No entry is made for the many-to-many association entry, because Change Tracker does not return the state of independent relationships. The second thing that Entity Framework did was to fix up the talk’s speaker collection.
When we call SaveChanges(), Entity Framework raises the overridden SaveChanges event. Inside this event, we update the CreateDate and RevisedDate properties. Before the SaveChanges() method is called, Entity Framework calls DetectChanges() to find any changes that occurred before. In Listing 8-10, we override the SaveChanges() method.
The DetectChanges() method relies on a snapshot base comparison of the original and current values for each property on each entity. This process determines what has changed in the object graph. For large object graphs, this comparison process may be time consuming.
8-8. Testing Domain Objects
Problem
You want to create unit tests for the business rules you have defined for your entities.
This type of recipe is often used when unit testing of specific data access functionality has to be performed.
Solution
For this solution, you’ll use the POCO template to generate the classes for your entities. Using the POCO template will reduce the amount of code you need to write, and it will make the solution a more clear. Of course, you will use the remaining steps in this solution with your handcrafted POCO classes.
Suppose you have a model like the one shown in Figure 8-9.
Figure 8-9. A model of reservations, schedules, and trains
This model represents reservations for train travel. Each reservation is for a particular scheduled train departure. To create the model and prepare the application for unit testing, do the following:
Listing 8-11. The IValidate Interface
public enum ChangeAction
{
Insert,
Update,
Delete
}
interface IValidate
{
void Validate(ChangeAction action);
}
Listing 8-12. Implementation of the IValidate Interface for the Reservation and Schedule Classes
public partial class Reservation : IValidate
{
public void Validate(ChangeAction action)
{
if (action == ChangeAction.Insert)
{
if (Schedule.Reservations.Count(r =>
r.ReservationId != ReservationId &&
r.Passenger == this.Passenger) > 0)
throw new InvalidOperationException(
"Reservation for the passenger already exists");
}
}
}
public partial class Schedule : IValidate
{
public void Validate(ChangeAction action)
{
if (action == ChangeAction.Insert)
{
if (ArrivalDate < DepartureDate)
{
throw new InvalidOperationException(
"Arrival date cannot be before departure date");
}
if (LeavesFrom == ArrivesAt)
{
throw new InvalidOperationException(
"Can't leave from and arrive at the same location");
}
}
}
}
Listing 8-13. Overriding the SaveChanges() Method
public partial class EFRecipesEntities
{
public override int SaveChanges()
{
this.ChangeTracker.DetectChanges();
var entries = from e in this.ChangeTracker.Entries().Where(e => e.State == (System.Data.Entity.EntityState.Added | EntityState.Modified | EntityState.Deleted))
where (e.Entity != null) &&
(e.Entity is IValidate)
select e;
foreach (var entry in entries)
{
switch (entry.State)
{
case EntityState.Added:
((IValidate)entry.Entity).Validate(ChangeAction.Insert);
break;
case EntityState.Modified:
((IValidate)entry.Entity).Validate(ChangeAction.Update);
break;
case EntityState.Deleted:
((IValidate)entry.Entity).Validate(ChangeAction.Delete);
break;
}
}
return base.SaveChanges();
}
}
Listing 8-14. Use this IReservationContext to Define the Methods You’ll Need from the DbContext
public interface IReservationContext : IDisposable
{
IDbSet<Train> Trains { get; }
IDbSet<Schedule> Schedules { get; }
IDbSet<Reservation> Reservations { get; }
int SaveChanges();
}
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> :
DbContext,IReservationContext
Listing 8-15. The ReservationRepository Class That Takes an IReservationContext in the Constructor
public class ReservationRepository: IDisposable
{
private IReservationContext _context;
public ReservationRepository(IReservationContext context)
{
if (context == null)
throw new ArgumentNullException("context is null");
_context = context;
}
public void AddTrain(Train train)
{
_context.Trains.Add(train);
}
public void AddSchedule(Schedule schedule)
{
_context.Schedules.Add(schedule);
}
public void AddReservation(Reservation reservation)
{
_context.Reservations.Add(reservation);
}
public void SaveChanegs()
{
_context.SaveChanges();
}
public List<Schedule> GetActiveSchedulesForTrain(int trainId)
{
var schedules = from r in _context.Schedules
where r.ArrivalDate.Date >= DateTime.Today &&
r.TrainId == trainId
select r;
return schedules.ToList();
}
}
Listing 8-16. The Implementation of the Fake Object Set and Fake Object Context
public class FakeDbSet<T> : IDbSet<T>
where T : class
{
HashSet<T> _data;
IQueryable _query;
public FakeDbSet()
{
_data = new HashSet<T>();
_query = _data.AsQueryable();
}
public virtual T Find(params object[] keyValues)
{
throw new NotImplementedException("Derive from FakeDbSet<T> and override Find");
}
public void Add(T item)
{
_data.Add(item);
}
public void Remove(T item)
{
_data.Remove(item);
}
public void Attach(T item)
{
_data.Add(item);
}
public void Detach(T item)
{
_data.Remove(item);
}
Type IQueryable.ElementType
{
get { return _query.ElementType; }
}
System.Linq.Expressions.Expression IQueryable.Expression
{
get { return _query.Expression; }
}
IQueryProvider IQueryable.Provider
{
get { return _query.Provider; }
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _data.GetEnumerator();
}
}
public class FakeReservationContext : IReservationContext, IDisposable
{
private IDbSet<Train> trains;
private IDbSet<Schedule> schedules;
private IDbSet<Reservation> reservations;
public FakeReservationContext()
{
trains = new FakeDbSet<Train>();
schedules = new FakeDbSet<Schedule>();
reservations = new FakeDbSet<Reservation>();
}
public IDbSet<Train> Trains
{
get { return trains; }
}
public IDbSet<Schedule> Schedules
{
get { return schedules; }
}
public IDbSet<Reservation> Reservations
{
get { return reservations; }
}
public int SaveChanges()
{
foreach (var schedule in Schedules.Cast<IValidate>())
{
schedule.Validate(ChangeAction.Insert);
}
foreach (var reservation in Reservations.Cast<IValidate>())
{
reservation.Validate(ChangeAction.Insert);
}
return 1;
}
public void Dispose()
{
}
}
Listing 8-17. The Unit Tests for Our Tests Project
[TestClass]
public class ReservationTest: IDisposable
{
private IReservationContext _context;
[TestInitialize]
public void TestSetup()
{
var train = new Train { TrainId = 1, TrainName = "Polar Express" };
var schedule = new Schedule { ScheduleId = 1, Train = train,
ArrivalDate = DateTime.Now,
DepartureDate = DateTime.Today,
LeavesFrom = "Dallas",
ArrivesAt = "New York" };
var reservation = new Reservation { ReservationId = 1,
Passenger = "Phil Marlowe",
Schedule = schedule };
_context = new FakeReservationContext();
var repository = new ReservationRepository(_context);
repository.AddTrain(train);
repository.AddSchedule(schedule);
repository.AddReservation(reservation);
repository.SaveChanges();
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void TestForDuplicateReservation()
{
var repository = new ReservationRepository(_context);
var schedule = repository.GetActiveSchedulesForTrain(1).First();
var reservation = new Reservation { ReservationId = 2,
Schedule = schedule,
Passenger = "Phil Marlowe" };
repository.AddReservation(reservation);
repository.SaveChanges();
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void TestForArrivalDateGreaterThanDepartureDate()
{
var repository = new ReservationRepository(_context);
var schedule = new Schedule { ScheduleId = 2, TrainId = 1,
ArrivalDate = DateTime.Today,
DepartureDate = DateTime.Now,
ArrivesAt = "New York",
LeavesFrom = "Chicago" };
repository.AddSchedule(schedule);
repository.SaveChanges();
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void TestForArrivesAndLeavesFromSameLocation()
{
var repository = new ReservationRepository(_context);
var schedule = new Schedule { ScheduleId = 3, TrainId = 1,
ArrivalDate = DateTime.Now,
DepartureDate = DateTime.Today,
ArrivesAt = "Dallas",
LeavesFrom = "Dallas" };
repository.AddSchedule(schedule);
repository.SaveChanges();
}
}
The Test project now has three unit tests that exercise the following business rules:
How It Works
With quite a lot of code, we’ve managed to build a complete solution that includes an interface (IReservationContext) that we can use to abstractly reference a DbContext, a fake DbSet (FakeDbSet<T>), a fake DbContext (FakeReservationContext), and a small set of unit tests. We use the fake DbContext so that our tests don’t interact with the database. The purpose of the tests is to validate our business rules, not to test the database interactions.
One key to the solution is that we created a simplified repository that managed the inserting and selecting of our objects. The constructor for this repository takes an IReservationContext. This subtle abstraction allows us to pass in an instance of any class that implements IReservationContext. To test our domain objects, we pass in an instance of FakeReservationContext. To allow our domain objects to be persisted to the database, we would pass in an instance of our real DbContext: EFRecipesEntities.
We need the DbSets returned by our fake DbContext to match the DbSets returned by the real EFRecipesEntities DbContext. To do this, we changed the T4 template that generates the context to return IDbSet<T> in place of DbSet<T>. We made sure our fake DbContext also returned DbSets of type IDbSet<T>. With this in place, we implemented our FakeDbSet<T> and derived it from IDbSet<T>.
In the Tests project, we set up the tests by creating a Reservation Repository based on an instance of the FakeReservationContext. The unit tests interact with the FakeReservationContext in place of the real DbContext.
Best Practice
There are two testing approaches that seem to work well for Entity Framework: Define a repository interface that both the real repository and one or more “testing” repositories implement. By hiding all of the interactions with the persistence framework behind the implementation of the repository interface, there is no need to create fake versions of any of the other infrastructure parts. This can simplify the implementation of the testing code, but it may leave parts of the repository itself untested.
Define an interface for the DbContext that exposes properties of type IDbSet<T> and a SaveChanges() method, as we have done in this recipe. The real DbContext and all of the fake DbContexts must implement this interface. Using this approach, you don’t need to fake the entire repository, which may be difficult in some cases. Your fake DbContexts don’t need to mimic the behavior of the entire DbContext class; that would be a real challenge. You do need to limit your code to just what is available on the interfaces.
8-9. Testing a Repository Against a Database
Problem
You want to test your repository against the database.
This type of recipe is often used when integration testing of whole-data access functionality has to be performed.
Solution
You have created a repository that manages all of the queries, inserts, updates, and deletes. You want to test this repository against a real instance of the underlying database. Suppose that you have a model like the one shown in Figure 8-10. Because we will create and drop the database during the tests, let’s start from the beginning in a test database.
Figure 8-10. A model of books in categories
To test your repository, do the following:
Listing 8-18. The BookRepository Class That Handles Inserts and Queries Against the Model
namespace BookRepository
{
public class BookRepository
{
private TestEntities _context;
public BookRepository(TestEntities context)
{
_context = context;
}
public void InsertBook(Book book)
{
_context.Books.Add(book);
}
public void InsertCategory(Category category)
{
_context.Categories.Add(category);
}
public void SaveChanges()
{
_context.SaveChanges();
}
public IQueryable<Book> BooksByCategory(string name)
{
return _context.Books.Where(b => b.Category.Name == name);
}
public IQueryable<Book> BooksByYear(int year)
{
return _context.Books.Where(b => b.PublishDate.Year == year);
}
}
}
Listing 8-19. BookRepositoryTest Class with the Unit Tests
[TestClass]
public class BookRepositoryTest
{
private TestEntities _context;
[ClassInitialize]
public void TestSetup()
{
_context = new TestEntities();
if (_context.DatabaseExists())
{
_context.DeleteDatabase();
}
_context.CreateDatabase();
}
[TestMethod]
public void TestsBooksInCategory()
{
var repository = new BookRepository.BookRepository(_context);
var construction = new Category { Name = "Construction" };
var book = new Book { Title = "Building with Masonary",
Author = "Dick Kreh",
PublishDate = new DateTime(1998, 1, 1) };
book.Category = construction;
repository.InsertCategory(construction);
repository.InsertBook(book);
repository.SaveChanges();
// test
var books = repository.BooksByCategory("Construction");
Assert.AreEqual(books.Count(), 1);
}
[TestMethod]
public void TestBooksPublishedInTheYear()
{
var repository = new BookRepository.BookRepository(_context);
var construction = new Category { Name = "Construction" };
var book = new Book { Title = "Building with Masonary",
Author = "Dick Kreh",
PublishDate = new DateTime(1998, 1, 1) };
book.Category = construction;
repository.InsertCategory(construction);
repository.InsertBook(book);
repository.SaveChanges();
// test
var books = repository.BooksByYear(1998);
Assert.AreEqual(books.Count(), 1);
}
}
How It Works
There are two common approaches to testing that are used with Entity Framework. The first approach is to test the business logic implemented in your objects. For this approach, you test against a “fake” database layer because your focus is on the business logic that governs the interactions of the objects and the rules that apply just before objects are persisted to the database. We illustrated this approach in Recipe 8-8.
A second approach is to test both the business logic and the persistence layer by interacting with the real database. This approach is more extensive, and also more costly in terms of time and resources. When it is implemented in an automated test harness, like the ones often used in a continuous integration environment, you need to automate the creation and dropping of the test database.
Each test iteration should start with a database in a known clean state. Subsequent test runs should not be affected by residue left in the database by previous tests. Dropping and creating databases together with the end-to-end code exercise requires more resources than the business logic only testing, as illustrated in Recipe 8-8.
In the unit tests in Listing 8-19, we checked to see whether the database exists in the Test Initialize phase. If the database exists, it is dropped with the DropDatabase() method. Next we create the database with the CreateDatabase() method. These methods use the connection string contained in the App.config file. This connection string would likely be different from the development database connection string. For simplicity, we used the same connection string for both.