Chapter 8. Using DbContext in Advanced Scenarios

The focus of this book so far has been to get you up and running with using the DbContext, along with its partner APIs—Validation and Change Tracking. Now it’s time to look at some advanced and less commonly used features of DbContext, the DbSet and the Database classes, as well as moving between a DbContext and ObjectContext. Even though this book is not an application patterns book, we will also take a look at two interesting application scenarios. One will be a discussion of defining your DbContext and taking into consideration the use of multiple contexts in your application to target only the sets of model classes that are needed in any given scenario. The other will be a look at leveraging the IDbSet to create abstractions that will allow you to build more flexible applications. In Programming Entity Framework, 2e, you’ll find an extensive sample that uses ObjectSet, automated unit testing, and repositories. This IDbSet example will be a slice of that, explaining how you can replicate the pattern using the DbContext API.

Moving Between ObjectContext and DbContext

As you’ve learned, the DbContext is a smaller API exposing the most commonly used features of the ObjectContext. In some cases, those features are mirrored in the DbContext API. In other cases, the Entity Framework team has simplified more complex coding by providing us with methods like Find or properties like DbSet.Local. But there’s a big API lurking underneath that you may still need access to. For example, you might want to work directly with the MetadataWorkspace to write generic code against classes because that API can read the model more efficiently than reflection. Additionally, the MetadataWorkspace is able to provide more information about the metadata than you can discover with reflection, for example, for Key properties. Or you might want to take advantage of a database-specific function that is exposed through Entity SQL, which you can’t access from LINQ to Entities. Or you may already have an application written using the ObjectContext and you want to leverage the DbContext in future updates without replacing all of the ObjectContext code.

All of these scenarios are achievable.

Note

You can learn about MetadataWorkspace, mentioned above, in Chapter 18 of Programming Entity Framework, 2e.

Accessing ObjectContext Features from a DbContext

If you are starting with a DbContext, you can access the ObjectContext features very easily through the IObjectContextAdapter. In fact, you’ve seen this done a few times in this book already. In Chapter 4 we used this to access the ObjectMaterialized event that is not available directly on DbContext.

Note

The Entity Framework team refers to the procedure of accessing the ObjectContext from DbContext as “dropping down to ObjectContext.” You have seen this expression used a few times already in this book.

The pattern to get to the ObjectContext is to cast the DbContext instance to this IObjectContextAdapter and, from there, access its ObjectContext property. DbContext is implemented as an explicit interface of IObjectContextAdapter, which is why you need to explicitly cast it:

 ((IObjectContextAdapter)context).ObjectContext

Once you have the ObjectContext in hand, you can work directly against that. This is not a new instance. DbContext wraps ObjectContext; the ObjectContext instance returned from IObjectContextAdapter is the instance that your DbContext was already using internally.

If you are writing a layered application and don’t want other developers on your team to worry about this implementation detail, you could create a property to allow them to get directly from the DbContext to the underlying ObjectContext.

For example, they may be aware that there are more advanced features available when they need them. You could wrap those into a property called Core. Here’s an example of a Core property that casts with the as operator:

public ObjectContext Core
{
  get
  {
    return (this as IObjectContextAdapter).ObjectContext;
  }
}

Now you can simply call Core from the DbContext instance to get at the desired features.

Adding DbContext into Existing .NET 4 Applications

What if you have an existing .NET 4 application that uses ObjectContext, but now you are extending the features of the application and would like to take advantage of the DbContext for new code? You can do this thanks to one of the overloads for instantiating a new DbContext. The overload allows you to pass in an existing ObjectContext instance.

The overload takes two parameters. The first is an ObjectContext instance and the second is a Boolean indicating whether or not the DbContext can dispose of the ObjectContext when the DbContext is disposed:

public DbContext(ObjectContext objectContext,
                 bool dbContextOwnsObjectContext)
{}

If you were to call this overload directly from the code that uses this context, that code would need to provide an ObjectContext instance each time as well as the bool value. Rather than force this onto the developer who is consuming the context, you can create a DbContext class that will do this automatically as well as expose the DbSets necessary for querying, updating, change tracking etc.

For the sake of demonstrating, we’ll start with a sample download from Programming Entity Framework, 2e. The WPF application from Chapter 26 uses a database-first EDMX with generated POCO classes and a hand-built ObjectContext class. This model is based on the BreakAway domain, as is the model we’ve used throughout this book.

The ObjectContext class, BAEntities, exposes a number of ObjectSets. Example 8-1 displays a subset of its code listing.

Example 8-1. A portion of the original BAEntities ObjectContext class
public partial class BAEntities : ObjectContext
{
  public const string ConnectionString = "name=BAEntities";
  public const string ContainerName = "BAEntities";

  public BAEntities()
    : base(ConnectionString, ContainerName)
  {
    this.ContextOptions.LazyLoadingEnabled = false;
    Initialize();
  }

  partial void Initialize();

  public ObjectSet<Activity> Activities
  {
    get { return _activities ??
          (_activities = CreateObjectSet<Activity>("Activities")); }
  }
  private ObjectSet<Activity> _activities;

  public ObjectSet<Contact> Contacts
  {
    get { return _contacts ??
          (_contacts = CreateObjectSet<Contact>("Contacts")); }
  }
  private ObjectSet<Contact> _contacts;

  public ObjectSet<Trip> Trips
  {
    get { return _trips ??
          (_trips = CreateObjectSet<Trip>("Trips")); }
  }
  private ObjectSet<Trip> _trips;

  public ObjectSet<Destination> Destinations
  {
    get { return _destinations ??
          (_destinations =
            CreateObjectSet<Destination>("Destinations")); }
  }
  private ObjectSet<Destination> _destinations;
}

In the project that contains this BAEntities class, we’ve added the EntityFramework package reference and a new class file, BAEntitiesDbContext.cs, that contains the BAEntitiesDbContext class. This new class does three important things:

  1. Inherits from DbContext

  2. Has a default constructor that calls a private constructor with the ObjectContext overload.

  3. Exposes DbSets to code against using the DbContext API.

Example 8-2 shows the listing for BaEntitiesDbContext. It includes DbSet properties to expose the classes exposed by the ObjectSet properties shown in Example 8-1. There are other DbSet properties in the class but they are not relevant to this example.

Example 8-2. DbContext class that wraps the BAEntities ObjectContext
using System.Data.Entity;
using System.Data.Objects;

namespace BAGA
{
 public class BAEntitiesDbContext: DbContext
  {
   public BAEntitiesDbContext():this(new BAEntities(),
                                 dbContextOwnsObjectContext:true)
   {
   }
   public DbSet<Activity> Activities{get;set;}
   public DbSet<Contact> Contacts {get;set;}
   public DbSet<Trip> Trips {get;set;}
   public DbSet<Destination> Destinations {get;set;}
  }
}

The constructor is a default constructor that takes no parameters, but its declaration invokes the base DbContext constructor overload that uses an ObjectContext.

The automated tests listed in Example 8-3 verify that, using this DbContext, we can retrieve, insert, and edit entities. Notice that the third test uses the DbSet.Find method as well. Because the database generates new key values for Trip, the second test verifies that the inserted Trip has a TripId greater than 0. The third test uses a similar assertion for the inserted Payment. The third test also re-retrieves the reservation from the database to check that its ModifiedDate value was updated.

Example 8-3. Automated tests to exercise the DbContext that wraps an existing ObjectContext
using System.Linq;
using System.Transactions;
using BAGA;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace DbContextTests
{
    [TestMethod]
    public void CanRetrieveTripViaDbContext()
    {
      using (var context = new BAEntitiesDbContext())
      {
        Assert.IsNotNull(context.Trips.FirstOrDefault());
      }
    }

    [TestMethod]
    public void CanInsertTripViaDbContext()
    {
      using (new TransactionScope())
      {
        var trip = new Trip
                     {
                       DestinationID = 55,
                       LodgingID = 1,
                       StartDate = new DateTime(2012, 1, 1),
                       EndDate = new DateTime(2012, 2, 1),
                       TripCostUSD = 1000
                     };
        using (var context = new BAEntitiesDbContext())
        {
          context.Trips.Add(trip);
          context.SaveChanges();
          Assert.IsTrue(trip.TripID > 0);
        }
      }
    }

    [TestMethod]
    public void CanRetrieveandModifyReservationandAddPayment()
    {
      using (new TransactionScope())
      {
        DateTime reservationDate;
        using (var context = new BAEntitiesDbContext())
        {
          //4 is a known reservation in the database
          var res = context.Reservations.Find(4);
          reservationDate = res.ReservationDate.AddDays(-1);
          res.ReservationDate = reservationDate;
          var payment = new Payment
                          {
                            Amount = 100,
                            ModifiedDate = DateTime.Now,
                            PaymentDate = DateTime.Now.Date
                          };
          res.Payments.Add(payment);
          context.SaveChanges();
          Assert.IsTrue(payment.PaymentID > 0);
        }
        using (var context = new BAEntitiesDbContext())
        {
          Assert.AreEqual(reservationDate,
             context.Reservations.Find(4).ReservationDate);
        }
      }
    }
  }
}

If you want to take advantage of DbContext when adding new features to an existing application that uses an ObjectContext, you can do so with the addition of a DbContext in the style of the BaEntitiesDbContext. Be sure to test your logic!

Leveraging SQL Server Operators Exposed in SqlFunctions

One scenario that we’ve been asked about recently was the ability to detect numeric data in some type of string column in the database. SQL Server has an IsNumeric function but there’s no way to express that in LINQ to Entities. A set of SQL Server specific functions was wrapped into System.Data.Objects.SQLClient.SqlFunctions in .NET4. These can be used in LINQ to Entities queries against the ObjectContext and against DbContext.

In the BreakAway domain, perhaps you need to search for numeric zip codes. Because postal codes can be alphanumeric in many parts of the world, the zip code field is a string and in the database, it’s an nvarchar.

To use IsNumeric in your query, you’ll need a using statement for the System.Data.Objects.SqlClient namespace at the top of your code file.

Then you can use SqlFunctions directly in the query expression. Example 8-4 demonstrates using the IsNumeric function to return a list of people with numeric zip codes. IsNumeric returns 1 for valid numbers, which is why the query searches for IsNumeric is equal to 1.

Example 8-4. Using SQL Server IsNumeric in LINQ to Entities
private static void UseSqlFunctions()
{
  using (var context = new BreakAwayContext())
  {
    var query=from p in context.People
              where SqlFunctions.IsNumeric(p.LastName)==1
              select p;
    var results=query.ToList();
  }
}

If you look at the query in a profiler, you can see that the IsNumeric function is used in the SQL query. Here are the last two lines of the SQL executed in the database:

FROM [dbo].[People] AS [Extent1]
WHERE 1 = (ISNUMERIC([Extent1].[ZipCode]))

Just be sure to keep in mind that this is specifically designed for use in SQL Server databases, though other providers could make similar functionality available for their databases.

Querying Derived Types with DbSet

If you have been working with ObjectContext and ObjectSet, you should be aware of another benefit of DbSet when working with derived types. You can create DbSets that encapsulate derived types. When working with ObjectSet, you are only able to create sets from a base type. So if you had a hierarchy such as Person with a derived type, Customer, any time you wanted to query Customer you would have to express a query starting with

context.People.OfType<Customer>()

This can get pretty tedious.

The DbContext API lets you create DbSet properties from derived types without having to declare the base type or set the OfType method to access the derived type. If you want to expose the derived type as a DbSet, you simply add it as you would any other entity in the model:

public DbSet<Customer> Customers { get; set; }

You can expose DbSet properties for base types and their derived types in your context. As with ObjectSet, querying a DbSet of the base type (for example, DbSet<Person>) will return all of the types in the hierarchy, including derived types that are exposed by their own DbSet property.

Understanding the Interface Property Limitation

Entity Framework is unable to create schema from interfaces. That means if you have any properties (complex type properties or navigation properties) that are interface types, Code First will not build those into the model. Your code may run without throwing an exception, but the database won’t have any schema to represent that type and therefore its data will not be persisted.

For example, you might have an IDestination interface and the Destination class and some other classes implementing that interface:

  public class Destination : IDestination
  public class EliteDestination : IDestination
  public class RoughingItDestination : IDestination

Then consider the Destination property in Lodging class. Rather than always returning a Destination instance, you might want to return any of those types:

[Required]
public IDestination Destination { get; set; }

Unfortunately, this scenario is just not supported.

We’ve seen some workarounds for this limitation, but they typically end up using one of the concrete implementations of the interface somewhere in the workaround and doing so means that you won’t be able to use any other implementations of the interface.

Note

Currently, providing support for mapped interface properties in Entity Framework is not high on the team’s priority list because there have been so few requests for this. If you want to get more attention to making Code First recognize interface properties, vote on (or submit) a suggestion at data.uservoice.com. Be sure you’re in the feedback area for Entity Framework.

Considering Automated Testing with DbContext

In this book, we’ve used a console application to demonstrate many of the features that you’ve learned about. This is to ensure that readers using the Express and Standard versions of Visual Studio are able to follow along. Our personal preference when building applications, however, is to include automated tests, whether we use the testing tools built into Visual Studio Professional and Visual Studio Ultimate, or third-party tools such as XUnit, NUnit, or the testing features in JetBrain’s Resharper.

To be able to build flexible tests, you’ll want to leverage the IDbSet interface. The DbSet class you’ve worked with throughout this book implements IDbSet. And the IDbSet interface is where the Add, Attach, Remove, and Create methods come from. IDbSet also implements IQueryable<T>, which enables LINQ and brings along the extension methods: Find, Include, and AsNoTracking.

First, let’s look at examples of automated tests that you can build and run with the existing BreakAwayContext class and without having to work with the IDbSet.

Testing with DbSet

You can build unit tests to validate that your classes work as expected without engaging Entity Framework or the database.

For example, the simple test listed in Example 8-5 checks that the FullName property in the Person type functions as expected.

Example 8-5. Ensuring that FullName works as expected
  [TestMethod()]
  public void PersonFullNameReturnsFirstNamePlusLastName()
  {
    var person = new Person
                   {
                     FirstName = "Roland",
                     LastName = "Civet"
                   };
    Assert.AreEqual(person.FullName, "Roland Civet");
  }

You can also write integrated tests that check to make sure some of your Entity Framework–related logic works as expected. Example 8-6 displays a test method that ensures you’ve configured your class correctly to cause Entity Framework validation to notice that a related Photo property is missing from a new Person.

Example 8-6. An integration test to ensure that your code and Entity Framework work together
[TestMethod]
public void ValidationDetectsMissingPhotoInPerson()
{
  var person = new Person
  {
    FirstName = "Mikael",
    LastName = "Eliasson"
  };
  DbEntityValidationResult result;
  using (var context = new BreakAwayContext())
  {
    result = context.Entry(person).GetValidationResult();
  }
  Assert.IsFalse(result.IsValid);
  Assert.IsTrue(result.ValidationErrors
    .Any(v => v.ErrorMessage.ToLower()
      .Contains("photo field is required")));
}

You can also write integration tests against custom logic that executes database queries or saves data back to the database to make sure the persistence is working as expected.

But there’s one area of testing that’s a bit trickier with Entity Framework, which is testing logic that uses the context to query and save data but does not necessarily need to make the trip to the database.

Exploring a Scenario That Unnecessarily Queries the Database

A simplistic example is a method that performs a database query based on some other logic. Perhaps you have a repository method to retrieve customers who have reservations for a trip, but only if that trip is in the future. Example 8-7 shows a single method from a repository class, GetTravelersOnFutureTrip. The class declaration and constructor are included in the listing for clarity.

Example 8-7. The GetTravelersOnFutureTrip method in the TripRepository class
public class TripRepository
{
  BreakAwayContext _context;

  public TripRepository(BreakAwayContext context)
  {
    _context = context;
  }
  public List<Person> GetTravelersOnFutureTrip(Trip trip)
  {
    if (trip.StartDate <= DateTime.Today)
    {
      return null;
    }

    return _context.Reservations
      .Where(r => r.Trip.Identifier == trip.Identifier)
      .Select(r => r.Traveler)
      .ToList();
  }
}

If a past trip is passed into the method, the method will return null. If a future trip is passed in, the method will query for the travelers on the trip and return a list of those travelers. Recall that the Reservation.Traveler property returns a Person type. If no reservations have been made for the trip, the list will contain zero items.

It would be feasible to test that the GetTravelersOnFutureTrip returns a null if the past trip is passed in and that it doesn’t return a null (regardless of the size of the list returned) if the trip is in the future.

Example 8-8 displays two tests to check both bits of logic.

Example 8-8. Testing logic of GetTravelersOnFutureTrip
[TestMethod]
public void GetCustomersOnPastTripReturnsNull()
{
  var trip = new Trip { StartDate = DateTime.Today.AddDays(-1) };
  using (var context = new BreakAwayContext())
  {
    var rep = new TripRepository(context);
    Assert.IsNull(rep.GetTravelersOnFutureTrip(trip));
  }
}

[TestMethod]
public void GetCustomersOnFutureTripDoesNotReturnNull()
{
  Database.SetInitializer(new
   DropCreateDatabaseIfModelChanges<BreakAwayContext>());
  var trip = new Trip { StartDate = DateTime.Today.AddDays(1) };
  using (var context = new BreakAwayContext())
  {
    var rep = new TripRepository(context);
    Assert.IsNotNull(rep.GetTravelersOnFutureTrip(trip));
  }
}

The first method, GetTravelersOnPastTripReturnsNull, will not hit the database. It creates a minimally populated trip instance with a past StartDate. The GetCustomersOnFutureTrip method sees that the StartDate is in the past and returns null, never reaching the query. Because the database will never be hit by the repository method, there’s no need to even worry about database initialization in this test.

In the second test, we expect to query the database, so we’re setting the initializer to DropCreateDatabaseIfModelChanges to ensure that the database exists. Since the test doesn’t care about the actual data, there’s no need to seed the database. We again create a minimal Trip, this time with a future StartDate. The repository method will execute the database query, requesting all Reservations where the TripIdentifier is 00000000-0000-0000-0000-000000000000 because we didn’t set that value in the Trip instance. There will be no results and the method will return an empty List<Person>. The test passes because an empty list is not equal to null. You can see that for this test, what’s in the database is of no consequence, so the trip to the database is a wasted effort. You only want to make sure that the method responds correctly to a future or past date. However, with the BreakAwayContext and its DbSets, there’s no avoiding the query if a future trip is passed in as a parameter.

If we build some more logic into the solution using the IDbSet interface, we’ll be able to take the database out of the picture for the tests.

Reducing Database Hits in Testing with IDbSet

If you want to test a method such as GetTravelersOnFutureTrip without hitting the database, you’ll need to use abstractions of the context and sets that do not involve database interaction. What we’ll show you is the key parts of a more abstracted solution so that we can focus on the IDbSet interface.

As an alternative to the BreakAwayContext class, we’ll create another context that can create entities on the fly without depending on the database. But in order for us to use this alternative context with the repository, it will need to let us execute the code in the GetTravelersOnFutureTrip method. That means it will need, for example, the ability to create and execute queries. IDbSet gives us the capabilities that we need.

Not only does the DbSet class we’ve been using implement IDbSet, it also inherits from DbQuery. And it’s the DbQuery class that adds in the reliance on the database. In our alternative context, we’ll use properties that implement IDbSet but are not derived from DbQuery. That means we’ll need a concrete implementation of IDbSet, giving us the ability to perform set logic and queries without interacting with a database, effectively “faking” the database interaction. So let’s start by creating this concrete class.

Creating an IDbSet Implementation

Because IDbSet implements IQueryable and IEnumerable, this new class will need to implement members of all three interfaces. Example 8-9 shows the entire listing of the FakeDbSet class including namespaces used by the class.

Note

The Visual Studio IDE, as well as third-party productivity tools, can help implement the interface members to reduce the typing.

Example 8-9. The FakeDbSet implementation of IDbSet
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace Testing
{
  public abstract class FakeDbSet<T> : IDbSet<T>
    where T : class, new()
  {
    readonly ObservableCollection<T> _items;
    readonly IQueryable _query;

    public FakeDbSet()
    {
      _items = new ObservableCollection<T>();
      _query = _items.AsQueryable();
    }

    public T Add(T entity)
    {
      _items.Add(entity);
      return entity;
    }

    public T Attach(T entity)
    {
      _items.Add(entity);
      return entity;
    }

    public TDerivedEntity Create<TDerivedEntity>()
      where TDerivedEntity : class, T
    {
      return Activator.CreateInstance<TDerivedEntity>();
    }

    public T Create()
    {
      return new T();
    }

    public abstract T Find(params object[] keyValues);

    public ObservableCollection<T> Local
    {
      get
      {
        return _items;
      }
    }

    public T Remove(T entity)
    {
      _items.Remove(entity);
      return entity;
    }

    public IEnumerator<T> GetEnumerator()
    {
      return _items.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
      return _items.GetEnumerator();
    }

    public Type ElementType
    {
      get { return _query.ElementType; }
    }

    public Expression Expression
    {
      get { return _query.Expression; }
    }

    public IQueryProvider Provider
    {
      get { return _query.Provider; }
    }
  }
}

Note

When calling DbSet.Attach, Entity Framework will throw an exception if the object you are attaching is already being tracked by the context. If this is important behavior for your FakeDbSet, you could implement some logic to emulate that behavior.

There are a number of notable points to make about this implementation.

The first is that FakeDbSet is an abstract class. That is due to another notable point, which is that there is an abstract method: Find. As we don’t anticipate having a DbContext to interact with, it will be too difficult to arrive at a generic way of handling IDbSet.Find for any entity. For example, the Trip type has a key named Identifier. So Find will need to build a query using Identifier, whereas for other types it might need to build a query around Id.

Note

Find is a member of the IDbSet interface. The Include, AsNoTracking, and Load methods, which you used earlier in this book, are extension methods on IQueryable. When using FakeDbSet or other IDbSet implementations, those methods will be run without throwing exceptions. But they won’t have any impact on your fake set or context. For example, a method that uses Include won’t emulate Include logic in your fake queries unless you implement special Include logic in your fake DbSets.

FakeDbSet contains two local fields: an ObservableCollection named _items and an IQueryable called _query. The _items is used to manage the data so that we can respond to the IDbSet methods such as Add and Remove as well as enumeration supplied by IEnumerable. We’re using ObservableCollection to make it easy to implement the Local property. The _query field is to support members of IQueryable: Expression and Provider.

Also notable are the implementations of Create. The Create methods are needed to create new entity instances when your entity types are using dynamic proxies. This allows the context to be aware of the instance. You learned about working with dynamic proxies in Chapter 3.

Note

While acting as a technical reviewer for this book, Mikael Eliasson took this implementation a step further to enable the generic FakeDbSet to be aware of the key property so that you’re not required to override the class simply to expand upon the Find method. See his solution at https://gist.github.com/783ddf75f06be5a29a9d.

Abstracting BreakAwayContext for Tests

There’s one more function to plan for, which is that the repository class currently expects a BreakAwayContext to be used to perform the query. The current BreakAwayContext brings with it the concrete DbSets and therefore the database. We’ll abstract the BreakAwayContext class by creating an interface that matches the contract (or expectation) of what should be in a BreakAwayContext class. Then we can tell the repository to expect anything that implements the interface, not just the concrete one it’s using now:

public interface IBreakAwayContext
{
  IDbSet<Destination> Destinations { get; }
  IDbSet<Lodging> Lodgings { get;}
  IDbSet<Trip> Trips { get; }
  IDbSet<Person> People { get; }
  IDbSet<Reservation> Reservations { get; }
  IDbSet<Payment> Payments { get; }
  IDbSet<Activity> Activities { get; }

  int SaveChanges();
}

Note

This interface is only demonstrating what’s needed to satisfy the repository method and to make sure existing BreakAwayContext examples from this chapter continue to function. As you build out an application and repositories, you’ll need more features, whether they are built into this particular interface or other interfaces and classes.

Notice that the interface returns IDbSet properties instead of concrete classes. This is how it will be possible to create a context that explicitly builds and returns FakeDbSets.

Now you can modify the repository class so that it expects an IBreakAwayContext instead of the BreakAwayContext class. Example 8-10 shows the revised first eight lines of the TripRepository class. The _context will now be an IBreakAwayContext.

Example 8-10. Beginning of revised TripRepository class
public class TripRepository
{
  IBreakAwayContext _context;

  public TripRepository(IBreakAwayContext context)
  {
    _context = context;
  }

If you want to use BreakAwayContext here, it will now need to implement the interface. First, you need to add the interface implementation into the class declaration. And because BreakAwayContext is now implementing the interface, it needs to match the interface. Destinations now must return an IDbSet<Destination> and so forth. You can see the relevant portion of the revised BreakAwayContext class in Example 8-11.

Example 8-11. BreakAwayContext revised to implement IBreakAwayContext
public class BreakAwayContext : DbContext, IBreakAwayContext
{
  public IDbSet<Destination> Destinations { get; set; }
  public IDbSet<Lodging> Lodgings { get; set; }
  public IDbSet<Trip> Trips { get; set; }
  public IDbSet<Person> People { get; set; }
  public IDbSet<Reservation> Reservations { get; set; }
  public IDbSet<Payment> Payments { get; set; }
  public IDbSet<Activity> Activities { get; set; }

If you were to rerun the GetCustomersOnFutureTripReturnsListOfPeople and GetCustomersOnPastTripReturnsNull tests, they will still pass. The repository is happy to have the BreakAwayContext instantiated in the tests because that class implements IBreakAwayContext. And the DbContext that BreakAwayContext derives from will ensure that the IDbSet properties return DbSet types so that you continue to interact with the database.

Now it’s time to focus on creating a context that you can use for testing that won’t hit the database. When you create a new context class that implements IBreakAwayContext, you’ll get all of the IDbSet properties. Since our repository method doesn’t need access to all of the IDbSet’s, the code in Example 8-12 only initializes the one that we’ll be working with—Reservations.

Example 8-12. FakeBreakAwayContext class
using System.Data.Entity;
using DataAccess;
using Model;

namespace Testing
{
  public class FakeBreakAwayContext : IBreakAwayContext
  {
    public FakeBreakAwayContext()
    {
      Reservations = new ReservationDbSet();
    }

    public IDbSet<Destination> Destinations { get; private set; }
    public IDbSet<Lodging> Lodgings { get; private set; }
    public IDbSet<Trip> Trips { get; private set; }
    public IDbSet<Person> People { get; private set; }
    public IDbSet<Reservation> Reservations { get; private set; }
    public IDbSet<Payment> Payments { get; private set; }
    public IDbSet<Activity> Activities { get; private set; }

    public int SaveChanges()
    {
      return 0;
    }
  }
}

What you don’t see yet in the listing is how the fake sets are populated; we’ll leave it up to the automated tests to provide relevant data where necessary. The tests we’re currently focused on won’t even require any seed data.

And now for the derived FakeDbSet classes, which are key to the FakeBreakAwayContext class. Each has its own way to implement Find based on knowledge of its key field. Example 8-13 shows the ReservationDbSet class needed for this example. You can use this as a basis for the others. Just be sure to use the key properties in the Find method (for example, DestinationId in DestinationDbSet). You are not required to implement the other fake sets to follow along with the rest of this chapter.

Example 8-13. ReservationDbSet deriving from and overriding FakeDbSet
using System;
using System.Linq;
using Model;

namespace Testing
{
  public class ReservationDbSet : FakeDbSet<Reservation>
  {
    public override Reservation Find(params object[] keyValues)
    {
      var keyValue = (int)keyValues.FirstOrDefault();
      return this.SingleOrDefault(r => r.ReservationId == keyValue);
    }
  }
}

This is one approach to abstracting the DbSets. Another idea is presented in the sidebar.

To see this in action, you’ll need to modify the tests so that they use the FakeBreakAwayContext instead of the BreakAwayContext. The updated listing is shown in Example 8-14.

Example 8-14. Automated test that uses the fake context, fake sets, and fake data
[TestMethod]
public void FakeGetCustomersOnPastTripReturnsNull()
{
  var trip = new Trip { StartDate = DateTime.Today.AddDays(-1) };
  var context = new FakeBreakAwayContext();
  var rep = new TripRepository(context);
  Assert.IsNull(rep.GetTravelersOnFutureTrip(trip));
}

[TestMethod]
public void FakeGetCustomersOnFutureTripDoesNotReturnNull()
{
  var trip = new Trip { StartDate = DateTime.Today.AddDays(1) };
  var context = new FakeBreakAwayContext();
  var rep = new TripRepository(context);
  Assert.IsNotNull(rep.GetTravelersOnFutureTrip(trip));
}

Now you can rerun the tests. You can debug them to verify that the tests are indeed using the FakeBreakAwayContext.

Now it’s possible to verify that the GetCustomersOnFutureTrip method in the TripRepository functions properly without involving the database.

Reviewing the Implementation

We covered a lot of ground in this section, so let’s catch our breath and make sure you haven’t lost sight of the big picture. When writing automated tests, it’s not uncommon to test logic in a method that, in addition to the logic your test is concerned with, also happens to interact with the database. In order to avoid this, we created a way to fake the database interaction leveraging the IDbSet interface that’s provided in the DbContext API. We created a concrete implementation of IDbSet called FakeDbSet, which is generic so that we can create FakeDbSets of any of our model types. In order to use non-database-oriented implementations of IDbSet, we also abstracted the BreakAwayContext into its own interface that returns IDbSet for querying then implemented FakeDbContext from there.

Figure 8-1 shows how the FakeBreakAwayContext context and the BreakAwayContext both implement the IBreakAwayContext interface. BreakAwayContext uses DbSet properties for direct interaction with the database, while FakeBreakAwayContext works with FakeDbSets populated with objects created in memory.

Classes implementing the IBreakAwayContext interface
Figure 8-1. Classes implementing the IBreakAwayContext interface

With these abstractions in place, TripRepository will work with any implementation of IBreakAwayContext. For the sake of the AutomatedTripTests, the test methods use the FakeBreakAwayContext and avoid database interaction while the application uses instances of BreakAwayContext to pass into the repository.

Supplying Data to a FakeDbSet

These test methods did not need any data available to do their jobs, but you may have tests that do require some fake data. Example 8-15 shows a new method that returns the count of Reservations for a single Trip.

Example 8-15. Repository method to retrieve a reservation count
public int ReservationCountForTrip(Trip trip)
{
  return _context.Reservations
    .Where(r => r.Trip.Identifier == trip.Identifier)
    .Count();
}

In order to test this method, the fake context will need to contain data. You can supply that data as part of a test. Example 8-16 demonstrates one such test, ReservationCountForTripReturnsCorrectNumber.

Example 8-16. Testing the ReservationCountForTrip method
[TestMethod]
public void ReservationCountForTripReturnsCorrectNumber()
{
  var context = new FakeBreakAwayContext();

  var tripOne = new Trip { Identifier = Guid.NewGuid() };
  var tripTwo = new Trip { Identifier = Guid.NewGuid() };

  context.Reservations.Add(new Reservation { Trip = tripOne });
  context.Reservations.Add(new Reservation { Trip = tripOne });
  context.Reservations.Add(new Reservation { Trip = tripTwo });

  var rep = new TripRepository(context);
  Assert.AreEqual(2, rep.ReservationCountForTrip(tripOne));
}

The context will need to contain some Reservation data in order to return a count. While you might consider adding a number of Reservation instances that are fully described with Payments, a Traveler, and a Trip, you can see in the example that only the most minimal information is required to perform the test accurately. Three new Reservations are added to the context but each Reservation contains no more information than its Trip property. Notice also that we created two Reservations with the Trip we are searching for and one Reservation assigned to another Trip. Once the context is seeded, we can use the assertion to verify that the query is properly filtering, not simply returning all of the Reservations that we created.

Depending on the logic you are testing, you can build up more complex fake data in your test.

Accessing the Database Directly from DbContext

DbContext communicates with the database any time you execute a query or call SaveChanges. You can take advantage of DbContext’s access to the database through its Database property. With DbContext.Database, you can communicate directly with the database if your application calls for such interaction.

You may not have realized that Code First leverages the Database property for the database initialization tasks. It uses the Database.Exists property to check for the database. Database has a Delete method and a Create method and even one called CreateIfNotExists. All four of these members are public, so you could use them directly if you want to. In fact, early technical previews of Code First required developers to use those properties and methods to perform database initialization manually. It wasn’t until later that the SetInitializer classes were introduced which encapsulated the most common initialization workflows.

Once your DbContext is instantiated, it will be aware of the connection string it will use to work with the database, even if you haven’t yet performed any tasks that would initiate the connection. You can use the DbContext.Database property to interact with the connection or the database itself.

Not all of the Database members require direct interaction with the database. For example, here is some code that writes the connection string of the Database associated with that context to a console window using the Database.Connection property, which returns a System.Data.Common.DbDataConnection:

using (var context=new BreakAwayContext())
{
  Console.WriteLine(context.Database.Connection.ConnectionString);
}

A more common use of the Database property is to execute raw queries and commands directly on the database for those odd occasions when you need to perform a query that’s not supported by your model or by Entity Framework.

Executing Queries with Database.SqlQuery and DbSet.SqlQuery

DbContext.Database.SqlQuery lets you execute a query on the database and return any type that you specify. It will use the connection from the context that you are calling Database.SqlQuery from. For example, the database might have a view named DestinationSummaryView. Even if there is no DestinationSummary model type, you can declare the class and then query the view using SqlQuery. As long as the results of the SqlQuery match the type that you want it to populate, Entity Framework will be happy.

The DestinationSummary class might look something like Example 8-17.

Example 8-17. DestinationSummary class
public class DestinationSummary
{
  public int DestinationId { get; set; }
  public string Name { get; set; }
  public int LodgingCount { get; set; }
  public int ResortCount { get; set; }
}

Then you can call the Database.SqlQuery from an instance of DbContext as follows:

var summary = context.Database.SqlQuery<DestinationSummary>(
"SELECT * FROM dbo.DestinationSummaryView");

SqlQuery returns an SqlDbQuery. You’ll have to execute the SqlQuery with a LINQ method such as ToList to execute the query.

If your query is returning types that are exposed in the context through DbSet properties, you can use the DbSet.SqlQuery method instead. This version of SqlQuery does not require you to supply the return type as a parameter, since the DbSet knows the type. Example 8-18 shows DbSet.SqlQuery, which demonstrates how explicit you need to be when constructing the query. The Destination maps to the baga.Locations table and that table’s field names don’t match the Destination property names. SqlQuery expects the results to match the type exactly, including matching the names and types correctly. The order of the columns in the result set is not critical.

Example 8-18. Executing a query with SqlQuery
var dests = context.Database.SqlQuery<Destination>
    (@"SELECT LocationId as DestinationId, LocationName as Name,
       Description, Country, Photo
       FROM baga.locations where country={0}","Australia");
 var results = dests.ToList();

The resulting dests will be a List of Destination types. Because of the string Format syntax (that is, {0}), Entity Framework will execute this in the database as a parameterized query.

Warning

SqlQuery is not supported for types that contain complex type properties. Therefore you cannot return a Person type from a SqlQuery because it has a PersonalInfo field and an Address field that are both complex types.

If you need to build dynamic queries, we recommend that you be conscientious of the possibility of SQL injection or other security attacks. You should build parameterized queries as you did in Example 8-18, rather than concatenate values directly into the SQL. Alternatively, you can use an overload of SqlQuery that accepts SqlParameters. Example 8-19 shows how you would use parameters with SqlQuery. The particular query could be performed very easily with LINQ to Entities and is only used here for demonstration purposes.

Example 8-19. Executing a query with SqlQuery
var destSql = @"SELECT LocationId as DestinationId,
                       LocationName as Name, Description,
                       Country,Photo
                       FROM baga.locations
                       WHERE country=@country";
var dests = context.Database.SqlQuery<Destination>
  (destSql, new SqlParameter("@country", "Australia"))
  .ToList();

Earlier in this chapter, we modified BreakAwayContext to use IDbSet properties instead of concrete DbSet properties. SqlQuery is a method of DbSet. If you are using IDbSet, you’ll first need to cast it to DbSet in order to use DbSet.SqlQuery. That means that for methods that include SqlQuery, you’ll be limited with respect to what types of automated tests you can perform.

Here is the SqlQuery statement from Example 8-19 revised to work with an IDbSet that needs to be cast to DbSet:

var dests = ((DbSet<Destination>)context.Destinations)
          .SqlQuery(destSql, new SqlParameter("@country", "Brazil"))
          .ToList();

Tracking Results of SqlQuery

When you execute a SqlQuery from Database, the results are never tracked by the context, even if the query returns types that are in the model and known by the context. If you do not want the results to be change-tracked, use DbContext.Database.SqlQuery.

Results of a DbSet.SqlQuery will be tracked by the context. Ensuring that results are change-tracked is the primary reason you would choose to use DbSet.SqlQuery over Database.SqlQuery.

Executing Commands from the Database Class

DbContext.Database also lets you execute commands, not just queries, on the database directly if you encounter an odd function you want to perform in the database. You can do this with the ExecuteSqlCommand method. ExecuteSqlCommand takes two parameters: a SQL string expression and an array of SqlParameters. Although as you saw with the SqlQuery, you might prefer using the cleaner-looking string Format syntax to achieve parameterized commands. ExecuteSqlCommand returns an int representing the number of rows affected in the database.

Like SqlQuery, ExecuteSqlCommand will use the connection of the context from which you are calling the command. Depending on the permissions granted for the active context instance, you could execute Insert, Update, and Delete commands or even commands that affect the database schema.

ExecuteSqlCommand is designed to handle special scenarios and isn’t meant as a replacement for Entity Framework’s main mechanisms for persisting data in the database.

If you have read Programming Entity Framework: Code First, you may recall how ExecuteSqlCommand was used to enhance seeding a database during initialization. See the sidebar.

Providing Multiple Targeted Contexts in Your Application

So far in this book we’ve used a single context class, BreakAwayContext, to represent our model and expose all of our domain classes for a solution’s data access needs. In a large solution, it is likely that you have different areas of your application that address a specific business process and will only require interaction with a subset of your domain classes. If you have a lot of domain classes, there are a number of benefits to creating DbContexts that are targeted to these various processes rather than one all-purpose context. Most important is maintainability. As your application grows, so will the DbContext class. It can become unwieldy if it’s responsible for many DbSet properties and fluent configurations for many classes. Adding and modifying existing logic will get more difficult. If you have multiple contexts, each responsible for a certain function of your application, they will each contain a smaller set of properties and configurations. It will be much easier to maintain each context as well as locate the logic you need within it.

Performance is another consideration. When Entity Framework creates an in-memory model of the context, the larger the context is the more resources are expended to generate and maintain that in-memory model.

Reusing Classes, Configurations, and Validation Across Multiple Contexts

Throughout this book you’ve seen us attempt to organize and refactor code as our application grew. From the beginning, we’ve kept the domain classes in their own project. If you have read Programming Entity Framework: Code First, you saw that when we used the Fluent API to configure our entities, we created separate classes to contain the Fluent configuration logic for each type. In the validation chapters, you saw that we encapsulated logic from the ValidateEntity method so that the method wouldn’t get loaded down with details of each custom validation being performed. Organizing logic in this way makes it easier to reuse that logic and lets us share classes and logic across our multiple contexts.

Note

These smaller contexts follow a pattern critical to Domain Driven Design called Bounded Contexts. I like to think of the technique of aligning the design of each individual small DbContext class with its related Domain Context as Bounded DbContexts.

Using the BreakAway domain, let’s look at a concrete example.

Let’s say that the Sales department is responsible for selling reservations. They would need to add reservations and payments. Sales does not design trips or create relationships with lodgings. Sales would need to perform a bit of customer service as well. If the person purchasing the reservation is a new customer, Sales would need to add the customer. Sales would need to look up trips and destinations but only as a lookup list. They wouldn’t need to work with Trip and Destination entities in a way that would enable change-tracking and modification.

In a simple scenario, this means Sales would need access to

  • Person (Lookup, Add, Edit)

  • Address (View from Person, Add, Edit)

  • Reservations (Lookup, Add, Edit)

  • Payments (View from Reservation, Add)

  • Read-Only Lists: Trip Dates, Destination Name, Activities

What about the Trip Planning department? They need access to

  • Trip (Lookup, Add, Edit)

  • Destination (Lookup, Add, Edit)

  • Activity (Lookup, Add, Edit)

  • Lodging (Lookup, Add, Edit)

  • Person (Lookup, Add, Edit)

What might the contexts for these two application scenarios look like?

SalesContext could have DbSet properties for People, Reservations, and Trips. This would allow sales to look up or add People and their Addresses; look up and add Reservations and their properties; and search for Trips along with their Destination, Lodging, and Activity details. SalesContext can draw from the pool of available classes in the Model project. Example 8-20 shows a minimal SalesContext class.

Example 8-20. SalesContext class
public class SalesContext:DbContext
{
  public DbSet<Person> People { get; set; }
  public DbSet<Reservation> Reservations { get; set; }
  public DbSet<Trip> Trips { get; set; }
}

Let’s see what Code First draws into the model based on these three DbSets.

Here’s a small console method that instantiates the context, and then drills down to the ObjectContext to query for EntityTypes from conceptual model (CSDL) using the MetadataWorkspace API. MetadataWorkspace reads the in-memory metadata of the model and then lists them in the console window. You’ll need a using for the System.Data.Metadata.Edm namespace to use the EntityType class.

Example 8-21. Forcing model creation and listing types
private static void ForceContextModelCreation()
{
  using (var context = new SalesContext())
  {
    var entityTypes = ((IObjectContextAdapter)context).ObjectContext
      .MetadataWorkspace.GetItems<EntityType>(DataSpace.CSpace);
        
    foreach (var entityType in entityTypes)
    {
      Console.WriteLine(entityType.Name);
    }
  }
}

Below are the SalesContext model types as they are listed in the console window:

Person
Lodging
Destination
InternetSpecial
Resort
Hostel
PersonPhoto
Reservation
Trip
Activity
Payment

Not only do you see the three types returned by the DbSets (Person, Reservation, and Trip), but all related types as well. When building a model, Code First will pull in all types that are reachable by types in the model. For example, Trip has a relationship to Destination, so Destination class was pulled into the model as well. The complex types, Address and PersonInfo, are in the model but not represented in the screenshot. You can see them by requesting GetItems<ComplexType>(DataSpace.CSpace)from the MetadataWorkspace.

Code First used its convention to decide what types needed to be in the model. In this case, all of the types that it included make sense for this model. But the context is still simpler to work with, since it doesn’t have explicit DbSets for all of those types.

Note

If you are using the Fluent API to configure the model, be sure that mappings necessary for all of the classes in your model are included in your configurations, not just mappings for types represented by DbSets.

By default Code First did us a favor. It created a new SalesContext database. That’s not the desired effect. What we’ll want is to have the complete BreakAwayContext database available to us. Let’s set this problem aside for later in this chapter and instead, look at our other targeted context, TripPlanning:

public class TripPlanningContext : DbContext
{
  public DbSet<Trip> Trips { get; set; }
  public DbSet<Destination> Destinations { get; set; }
  public DbSet<Lodging> Lodgings { get; set; }
  public DbSet<Activity> Activities { get; set; }
  public DbSet<Person> People { get; set; }
}

Note

We’re using common types in both the SalesContext and TripPlanningContext. What about sharing entity instances across instances of these contexts? Read the sidebar at the end of the chapter (Sharing Types Across Multiple DbContexts) for some insight into this scenario.

Modify the ForceContextModelCreation method from Example 8-21 to instantiate the context variable as a TripPlanningContext rather than a SalesContext. The output of the modified method, showing the types in the TripPlanningContext model is as follows:

Trip
Destination
Lodging
InternetSpecial
Person
PersonPhoto
Reservation
Payment
Resort
Hostel
Activity

Code First pulled more entities into the model than we will need. We’ll want PersonPhoto for creating any new Person types. But there’s no need for Reservation and Payment types in the model.

In rare cases, it will be safe to use the Fluent API’s Ignore method to trim back entities that you don’t want in the model. But doing so could easily lead you to breaking relationship constraints and losing access to foreign key properties in a model.

Note

It is not recommended to remove entities from a model that were pulled in by Code First because they are reachable by another entity. There’s a good chance that you will create problems with relationship constraints and foreign keys, which may or may not be caught by exceptions. This could lead to invalid data in your database.

It won’t always be possible to ignore a class that you don’t need in the model. For example, if you decided to remove Person from TripPlanningContext, an exception will be thrown when Code First attempts to create the model. The reason is that there are configurations in the Lodging class that depend on the Person class. The DbModelBuilder will try to work that out but will fail because there’s no Person in the model.

Ensuring That All DbContexts Use a Single Database

Now let’s see how to use these small contexts against a single database.

By convention, the context will force Code First to look for a database with the same name as the context. That’s not what we want. We want them all to use the BreakAwayContext database. We could use a connection string in the config file to point to the correct database. But there’s another wrinkle. The context will look for a connection string that matches its name. We’d have to have a SalesContext connection, a TripPlanningContext connection, and additional connections specified in the config file for every context. That’s not very maintainable.

Another tool we have at our disposal is an overload of the context constructor. We can pass in a database name. That solves the problem; however, it means that every time we instantiate a new SalesContext or any other context in our solution, we’d have to pass in a string or a variable representing “BreakAwayContext”. That’s also undesirable. Not only is it unnecessarily repetitive, you have to worry about someone forgetting to use the overload. However, you could lean on a base class to apply the connection string for you each time.

Example 8-22 shows a handy generic base class pattern suggested by Arthur Vickers from the Entity Framework team. Not only will this base class ensure that the any context that inherits from it uses the appropriate connection, but by setting the initializer on the given context to null, it ensures that Code First won’t attempt to initialize a database for the context. Because the constructor is telling DbContext to look for a connection in the configuration file, you would need to add a connection string named “breakaway” to your app.config or web.config file.

Example 8-22. BaseContext to set connection string and disable database initialization
public class BaseContext<TContext> : DbContext
where TContext : DbContext
{
  static BaseContext()
  {
    Database.SetInitializer<TContext>(null);
  }

  protected BaseContext()
    : base("name=breakaway")
  {
  }
}

Note

Note that the BaseContext constructor is static. That ensures that the initializer setting is set per application instance of the given constructor, not per context instance. Initializer settings should be application-wide, so this is desirable.

You can then modify the context classes to inherit from BaseContext instead of inheriting from DbContext directly.

Here are the revised declarations for TripPlanningContext and SalesContext:

public class TripPlanningContext : BaseContext<TripPlanningContext>

public class SalesContext : BaseContext<SalesContext>

Validating Relationship Constraints and Other Validations with Multiple Contexts

Each context will be responsible for its own validations. The context will check to make sure relationship constraints between classes are satisfied. If you have required relationships between classes, if one of those classes is in a model, the other needs to be as well. You should take advantage of automated testing to make sure that your small models don’t break relationships.

If you have custom logic that needs to be triggered in ValidateEntity, be sure to put it in a project that is accessible by your different contexts. In Chapter 7, for example, Example 7-5 demonstrated calling out to a custom method, ValidateLodging from ValidateEntity. In any context class where you anticipate using Lodging types, you’ll probably want to call that ValidateLodging method. So rather than declare ValidateLodging a private method inside of the BreakAwayContext class file, you could make it public and put it into a separate project where it can be called from other context classes.

Getting Code First to Create Full BreakAwayContext Database

You still may want to leverage Code First’s database initialization while developing this application. Even though you don’t want the SalesContext or TripPlanningContext classes to be involved with database initialization, you could create a DbContext class just for the sake of database initialization. It might even involve Code First Migrations or a custom initializer with a Seed method. All you should need to do is create one context class that will involve all of the classes. You can ensure all of the classes are pulled into the model by creating DbSets for each of the entities rather than hoping that relationships and hierarchies between your domain classes will create all of the relevant tables in the database. If you are using fluent configurations, you’ll need to add all of them to the modelBuilder.Configurations to be sure that all of the mappings are created properly. In our test classes we have one test, which then recreates the database for us as needed:

private static void ForceBreakAwayDatabaseCreation()
{
  Database.SetInitializer(new InitializeBagaDatabaseWithSeedData());
  using (var context = new BreakAwayContext())
  {
    context.Database.Initialize(force: false);
  }
}

Note

Remember that the InitializeBagaDatabaseWithSeedData class currently derives from DropCreateDatabaseAlways.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset