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.
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.
You can learn about MetadataWorkspace
, mentioned above, in Chapter
18 of Programming
Entity Framework, 2e.
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
.
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.
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 DbSet
s 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
ObjectSet
s. Example 8-1 displays a subset
of its code listing.
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:
Inherits from DbContext
Has a default constructor that calls a private constructor
with the ObjectContext
overload.
Exposes DbSet
s 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.
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.
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!
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.
private static void
UseSqlFunctions() {using
(var
context =new
BreakAwayContext
()) {var
query=from
pin
context.Peoplewhere
SqlFunctions
.IsNumeric(p.LastName)==1select
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.
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 DbSet
s
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.
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.
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.
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
.
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.
[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
.
[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.
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.
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.
[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
DbSet
s, 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.
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.
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.
The Visual Studio IDE, as well as third-party productivity tools, can help implement the interface members to reduce the typing.
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
{ _query.ElementType; } }public
Expression
Expression {get
{return
_query.Expression; } }public
IQueryProvider
Provider {get
{return
_query.Provider; } } } }
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
.
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 DbSet
s.
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.
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.
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 DbSet
s
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(); }
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
.
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.
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
.
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.
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 DbSet
s. Another idea is presented in the
sidebar.
To see this in action, you’ll need to modify the tests so that
they use the FakeBreak
AwayContext
instead of the BreakAwayContext
. The updated listing is shown
in Example 8-14.
[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.
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 FakeDbSet
s 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 FakeDbSet
s populated with objects created in
memory.
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.
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 Reservation
s for a single Trip
.
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
.
[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 Reservation
s
are added to the context but each Reservation
contains no more information than its Trip
property.
Notice also that we created two Reservation
s 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 Reservation
s
that we created.
Depending on the logic you are testing, you can build up more complex fake data in your test.
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.
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.
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.
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.
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 SqlParameter
s. 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.
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();
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
.
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 SqlParameter
s. 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.
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.
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.
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.
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 DbSet
s.
Here’s a small console method that instantiates the context, and
then drills down to the ObjectContext
to query for EntityType
s 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.
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 DbSet
s (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 DbSet
s for all
of those types.
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 DbSet
s.
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
; } }
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.
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.
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.
public class
BaseContext
<TContext> :DbContext
where
TContext :DbContext
{static
BaseContext() {Database
.SetInitializer<TContext>(null
); }protected
BaseContext() :base
("name=breakaway"
) { } }
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
>
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.
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 DbSet
s 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
); } }
Remember that the InitializeBagaDatabaseWithSeedData
class
currently derives from DropCreateDatabaseAlways
.