Entity Framework was
supposed to solve the problem of Linq to SQL, which requires endless hacks to
make it work in n-tier world. Not only did Entity Framework solve none of the
L2S problems, but also it made it even more difficult to use and hack it for
n-tier scenarios. It’s somehow half way between a fully disconnected ORM and a
fully connected ORM like Linq to SQL. Some useful features of Linq to SQL are
gone – like automatic deferred loading. If you try to do simple select with join, insert, update, delete in a
disconnected architecture, you will realize not only you need to make
fundamental changes from the top layer to the very bottom layer, but also
endless hacks in basic CRUD operations. I will show you in this article how I have added custom CRUD functions on top of EF’s ObjectContext to make it
finally work well in a fully disconnected N-tier web application (my open source
Web 2.0 AJAX portal – Dropthings) and how I have
produced a 100% unit testable fully n-tier compliant data access
layer following the repository pattern.
In .NET 4.0, most of the problems are solved, but not all. So, you should
read this article even if you are coding in .NET 4.0. Moreover, there’s enough
insight here to help you troubleshoot EF related problems.
You might think “Why bother using EF when Linq to SQL is doing good enough
for me.” Linq to SQL is not going to get any innovation from Microsoft anymore.
Entity Framework is the future of persistence layer in .NET framework. All the
innovations are happening in EF world only, which is frustrating. There’s a big jump on
EF 4.0. So, you should plan to migrate your L2S projects to EF soon.
First you have to extend the ObjectContext that gets generated when you add
an ADO.NET Entity Data Model. I usually create a new class and then inherit from
the generated class. Something like this:
Next step is to set the MergeOption to NoTracking for each and
every entity. If you don’t do this, the entities are attached to the
ObjectContext’s
ObjectStateManager
by default, which tracks
changes to the entity so that it can save the changes when SaveChanges is
called. But that also means your entities are tied to something on the data
access layer and such tie is strong tie. As long as the entities are alive, the
tie exists. So, you need to detach the entities before you pass the entities
through the tiers. However, when you call the Detach(entity)
function, it
not only detaches the entity, but also the entire graph. That’s good and
expected. But what’s unexpected is, all the referenced entities and collections
are set to NULL. So, if you have to pass an entity to other tier and you have to
carry along all the referenced entities, there’s no way to do it in EF. That’s
why I have done this:
I am setting the MergeOption to NoTracking for all the entities in the
constructor. This makes all the entities to be by default “detached”, but still
keeps the references to child/parent entities/collections intact. They aren’t
put in the ObjectStateManager
.
entities to entity sets in a generic
function.
public void Attach<TEntity>(TEntity entity)
where TEntity : EntityObject
{
if (entity.EntityState != EntityState.Detached)
return;
ObjectStateEntry entry;
if (ObjectStateManager.TryGetObjectStateEntry(entity, out entry))
{
}
else
{
_AttachMethodCache[typeof(TEntity).Name](this, entity);
}
}
public void AddTo<TEntity>(TEntity entity)
{
_AddToMethodCache[typeof(TEntity).Name](this, entity);
}
Once these are done, you can now do regular CRUD operation.
Selecting parent, child, many-to-many, far ancestor entities
EF introduced new way of doing join between multiple entities. In Linq to
SQL, you would have done this:
from widgetInstance in dc.WidgetInstances
from widgetZone in dc.WidgetZones
join widgetInstance on widgetZone.ID equals widgetInstance.WidgetZoneID
where widgetZone.ID == widgetZoneId
orderby widgetInstance.OrderNo, widgetZone.ID
select widgetInstance
In EF, there’s no join. You can directly access a referenced entit:
from widgetInstance in dc.WidgetInstance
where widgetInstance.WidgetZone.ID == widgetZoneId
orderby widgetInstance.OrderNo, widgetInstance.WidgetZone.ID
select widgetInstance
This is a much simpler syntax and especially here you don’t need to know the
internals of doing join and which keys in two tables participates in the join.
It’s completely abstracted on the ObjectContext
design. It is good!
But getting entities from a many-to-many join was not so trivial. For
example, here’s a query in EF, which selects a related entity based on many to
many mapping.
from widgetInstance in dc.WidgetInstance
where widgetInstance.Id == widgetInstanceId
select widgetInstance.WidgetZone.Column.FirstOrDefault().Page
The entity model is like this:
Notice the FirstOrDefault()
call inside the select clause. That’s the
think does the many-to-many query on the Column
table and maps the Page
entity.
Similarly, say you want to select a far ancestor’s property from a great
great great great child. For example, in this diagram, you have the ID
of a
WidgetInstance
at the bottom left. Now you want to select only the
LoweredUserName of the ancestor entity User. You can see from the diagram that
there’s a parent object WidgetZone
, then that’s related to Page via a
many-to-many mapping using Column
, and then the Page
has a parent object User
.
And you want to select only the LoweredUserName
of User
.
For this the query in EF is like this:
from widgetInstance in dc.WidgetInstance
where widgetInstance.Id == widgetInstanceId
select widgetInstance.WidgetZone.Column.FirstOrDefault().Page.aspnet_Users.LoweredUserName
If you wanted to do this with Linq to SQL, you would have ended up with a
query that’s the size of a small blog post.
Inserting a disconnected entity
Here’s the generic version of Insert<>
which can insert any
disconnected entity.
public TEntity Insert<TEntity>(TEntity entity)
where TEntity : EntityObject
{
AddTo<TEntity>(entity);
this.SaveChanges(true);
this.Detach(entity);
return entity;
}
Here’s the small test that shows it works:
[Fact]
public void Creating_new_object_and_insert_should_work()
{
using (var context = new DropthingsEntities2())
{
var newWidgetZone = context.Insert(new WidgetZone
{
Title = "Hello",
UniqueID = Guid.NewGuid().ToString(),
OrderNo = 0
});
Assert.NotEqual(0, newWidgetZone.ID);
Assert.Equal<EntityState>(EntityState.Detached, newWidgetZone.EntityState);
}
}
It confirms the entity is inserted, an auto identify is generated and
assigned to the entity as expected and also the entity is returned as detached,
which you can now pass through the tiers.
Inserting disconnected child entities
Inserting a disconnected child entity is so insanely different than other
popular ORM libraries, including Linq to SQL, that if you have a repository
layer, get ready for some massive refactoring. The common principle of inserting
a child entity is – first you have to attach the parent entity in the context,
then you will have to set the mapping between the parent and the child (you
can’t have the mapping already!), and then you will have to call SaveChanges
.
Here’s the code:
public TEntity Insert<TParent, TEntity>(
TParent parent,
Action<TParent, TEntity> addChildToParent,
TEntity entity)
where TEntity : EntityObject
where TParent : EntityObject
{
AddTo<TParent, TEntity>(parent, addChildToParent, entity);
this.SaveChanges();
this.AcceptAllChanges();
this.Detach(parent);
this.Detach(entity);
return entity;
}
private void AddTo<TParent, TEntity>(TParent parent,
Action<TParent, TEntity> addChildToParent,
TEntity entity)
where TEntity : EntityObject
where TParent : EntityObject
{
Attach<TParent>(parent);
addChildToParent(parent, entity);
}
Here’s a test case that shows how to use it:
[Fact]
public void Using_a_stub_parent_insert_a_child_object_should_work()
{
var userId = default(Guid);
using (var context = new DropthingsEntities2())
{
aspnet_Users existingUser = context.aspnet_Users.OrderByDescending(u => u.UserId).First();
userId = existingUser.UserId;
}
using (var context = new DropthingsEntities2())
{
var newPage = new Page
{
Title = "Dummy Page",
VersionNo = 0,
...
...
aspnet_Users = new aspnet_Users { UserId = userId },
};
var parentUser = newPage.aspnet_Users;
newPage.aspnet_Users = null;
context.Insert<aspnet_Users, Page>(
parentUser,
(user, page) => page.aspnet_Users = user,
newPage);
Assert.NotEqual(0, newPage.ID);
}
}
You must have spotted the horror and pain of inserting child object by now.
Say from some upper layer, you got an entity with parent entity already assigned
to it. Before you can insert it, you will have to first set the parent entity to
null and then attach it. If you don’t do it, then Insert silently fails. No
exception, you just get nothing happening. You will wonder yourself - the poor
EF Program Manager must have been severely underpaid.
If you search around, you will find various alternatives to this. Some tried
the context.SomeSet.AddObject(…)
approach. That works fine for one and only one
insert per context. But you cannot insert another entity of same type, having
the same parent entity using that approach. I have tried 4 different approaches,
this is the only one that works in all scenarios – both connected and
disconnected, having parent entities as real and stub, inserting one or more
within same context or in different contexts. I have literally written over
thousand lines of test codes to test all possible ways of inserting child
entities to prove this works. EF SDETs in Microsoft, here I come.
Inserting disconnected many-to-many entities
Just like child entities you can insert many-to-many mapping entities. You
can treat them as if they have two or more parents. For example, if you look at
this diagram:
Here the Column
is a many-to-many map Entity. So, it has two foreign keys –
one to WidgetZone
and the other to Page. In EF world, you can think Column
having two parents – WidgetZone
and Page. Thus the Insert<> is similar to
inserting child entity.
[Fact]
public void Many_to_many_entity_should_insert_in_same_context()
{
var page = default(Page);
var widgetZone = default(WidgetZone);
using (var context = new DropthingsEntities2())
{
page = context.Page.OrderByDescending(p => p.ID).First();
widgetZone = context.WidgetZone.OrderByDescending(z => z.ID).First();
var columnNo = (int)DateTime.Now.Ticks;
var newColumn1 = new Column
{
ColumnNo = columnNo,
ColumnWidth = 33,
};
context.Insert<Page, WidgetZone, Column>(page, widgetZone,
(p, c) => p.Column.Add(c),
(w, c) => w.Column.Add(c),
newColumn1);
Assert.NotEqual(0, newColumn1.ID);
}
}
Here you can see, just like a single parent-child insert, it’s doing a double
parent-child insert. The code in the Insert<>
is as following:
public TEntity Insert<TParent1, TParent2, TEntity>(
TParent1 parent1, TParent2 parent2,
Action<TParent1, TEntity> addChildToParent1,
Action<TParent2, TEntity> addChildToParent2,
TEntity entity)
where TEntity : EntityObject
where TParent1 : EntityObject
where TParent2 : EntityObject
{
AddTo<TParent1, TParent2, TEntity>(parent1, parent2, addChildToParent1, addChildToParent2, entity);
this.SaveChanges(true);
this.Detach(parent1);
this.Detach(parent2);
this.Detach(entity);
return entity;
}
private void AddTo<TParent1, TParent2, TEntity>(TParent1 parent1, TParent2 parent2, Action<TParent1, TEntity> addChildToParent1, Action<TParent2, TEntity> addChildToParent2, TEntity entity)
where TEntity : EntityObject
where TParent1 : EntityObject
where TParent2 : EntityObject
{
Attach<TParent1>(parent1);
Attach<TParent2>(parent2);
addChildToParent1(parent1, entity);
addChildToParent2(parent2, entity);
}
Again there are several ways to do this kind of Insert available on the web
and I have tried many of them. Most of them fails when you try to insert
multiple child entities using the same context. This one is proven to work. I
got hundreds lines to test code to back my claim.
Updating a disconnected entity
Update is not straightforward as Insert. First you will have to Attach the
disconnected entity and all referenced entities, keeping in mind they might
already exist in ObjectStateManager
and thus trying to attach the entity will
result in the dreaded:
An object with the same key already exists in the ObjectStateManager.
The ObjectStateManager cannot track multiple objects with the same key
The common solution found on the internet for updating a disconnected entity
is like this, that works in most of the common scenarios, except one
not-so-common scenario. The common Update
function you see is like this:
public TEntity Update<TEntity>(TEntity entity)
where TEntity : EntityObject
{
Attach<TEntity>(entity);
SetEntryModified(this, entity);
this.SaveChanges(true);
return entity;
}
First it attaches the entity in the context. Then he SetEntryModified
function will go through all non-key properties of the entity and mark it as
modified, so that the context sees the object as modified and does an update
when SaveChanges
is called. SetEntryModified
is like this:
static void SetEntryModified(ObjectContext context, object item)
{
ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(item);
for (int i = 0; i < entry.CurrentValues.FieldCount; i++)
{
bool isKey = false;
string name = entry.CurrentValues.GetName(i);
foreach (var keyPair in entry.EntityKey.EntityKeyValues)
{
if (string.Compare(name, keyPair.Key, true) == 0)
{
isKey = true;
break;
}
}
if (!isKey)
{
entry.SetModifiedProperty(name);
}
}
}
This works fine when you load an entity in one context and then try to update
it in another context. For example, the following test code shows loading an
entity in one context, then disposing that context and trying to update in
another newly created context.
[Fact]
public void Entity_should_update_loaded_from_another_context()
{
int someValue = (int)DateTime.Now.Ticks;
WidgetInstance wi;
using (var context = new DropthingsEntities2())
{
wi = context.WidgetInstance.OrderByDescending(w => w.Id).First();
}
wi.Height = someValue;
using (var context = new DropthingsEntities2())
{
context.Update<WidgetInstance>(wi);
WidgetInstance wi2 = getWidgetInstance(context, wi.Id).First();
Assert.Equal(wi.Height, wi2.Height);
}
}
This works fine as expected. Even if you have loaded the entity with all
referenced entities, and then passed through the tiers and then got the entity
back for update without the original referenced entities, it works as well.
[Fact]
public void Entity_should_update_loaded_from_another_context_with_stub_referenced_entities()
{
int someValue = (int)DateTime.Now.Ticks;
WidgetInstance wi;
using (var context = new DropthingsEntities2())
{
wi = context.WidgetInstance.Include("WidgetZone").Include("Widget").OrderByDescending(w => w.Id).First();
}
wi.Height = someValue;
wi.WidgetZone = new WidgetZone { ID = wi.WidgetZone.ID };
wi.Widget = new Widget { ID = wi.Widget.ID };
using (var context = new DropthingsEntities2())
{
context.Update<WidgetInstance>(wi);
WidgetInstance wi2 = getWidgetInstance(context, wi.Id).First();
Assert.Equal(wi.Height, wi2.Height);
}
}
This is a typical n-tier scenario where you have one entity, without any of
the referenced entity. Now you are about to update it and you only have the
foreign keys at hand. So, you need to create stubs for the referenced entities
from the foreign keys so that you don’t need to hit database to read the entity
again along with all referenced entities. The above test passes for this
scenario as well, but the one that fails is close to this scenario. I will get
to that. Now see another scenario.
In a high volume n-tier app, you cache frequently used entities. Entities are
loaded from cache and then changed and updated in database. Something like
this:
[Fact]
public void Changes_made_to_entity_after_update_should_update_again()
{
int someValue = (int)DateTime.Now.Ticks;
MemoryStream cachedBytes = new MemoryStream();
using (var context = new DropthingsEntities2())
{
var wi = context.WidgetInstance.Include("WidgetZone").Include("Widget").OrderByDescending(z => z.Id).First();
var widget = context.Widget.Where(w => w.ID == wi.Widget.ID);
var widgetzone = context.WidgetZone.Where(zone => zone.ID == wi.WidgetZone.ID);
wi.Height = someValue;
var updated = context.Update<WidgetInstance>(wi);
Assert.NotNull(updated);
WidgetInstance wi2 = getWidgetInstance(context, wi.Id).First();
Assert.Equal(someValue, wi2.Height);
new DataContractSerializer(typeof(WidgetInstance)).WriteObject(cachedBytes, wi2);
}
var anotherThread = new Thread(() =>
{
cachedBytes.Position = 0;
var wi = new DataContractSerializer(typeof(WidgetInstance)).ReadObject(cachedBytes) as WidgetInstance;
Assert.NotNull(wi.Widget);
using (var context = new DropthingsEntities2())
{
someValue = someValue + 1;
wi.Height = someValue;
var updatedAgain = context.Update<WidgetInstance>(wi);
Assert.NotNull(updatedAgain);
WidgetInstance wi3 = getWidgetInstance(context, wi.Id).First();
Assert.Equal(someValue, wi3.Height);
}
});
anotherThread.Start();
anotherThread.Join();
}
Here I am simulating a caching scenario where the cache is an out-of-process
cache or a distributed cache. I am loading an entity along with its referenced
entities, just to be sure and then updating some properties. After the update,
the changed entity is serialized. Then in another context, the changed entity is
created from the serialized stream. This ensures the was no tie with the
original context. Moreover, it proves it works in distributed caches where the
objects aren’t stored in memory, but always serialized-deserialized. The test
proves in such caching scenario, update works fine.
Now the moment you have been waiting for. This particular scenario required
me to change the Update
method completely and choose a completely different
approach for updating entities.
Say you have to change one of the foreign keys. The way to do this in EF is
to change the referenced entity to a new stub. Something like this:
[Fact]
public void Changing_referenced_entity_should_work_just_like_updating_regular_entity()
{
int someValue = (int)DateTime.Now.Ticks;
WidgetInstance wi;
var newWidget = default(Widget);
using (var context = new DropthingsEntities2())
{
wi = context.WidgetInstance.Include("WidgetZone").Include("Widget").OrderByDescending(w => w.Id).First();
newWidget = context.Widget.Where(w => w.ID != wi.Widget.ID).First();
}
wi.Height = someValue;
wi.Widget = new Widget { ID = newWidget.ID };
using (var context = new DropthingsEntities2())
{
context.Update<WidgetInstance>(wi);
WidgetInstance wi2 = getWidgetInstance(context, wi.Id).First();
Assert.Equal(wi.Height, wi2.Height);
Assert.Equal(newWidget.ID, wi2.Widget.ID);
}
}
If you try this, you get no exception, but the changed foreign key does not
update. EF cannot see the changes made in the referenced entity. I thought I
will have to change the WidgetReference
as well to reflect the change
made in the Widget
property. Something like this:
wi.Height = someValue;
wi.Widget = new Widget { ID = newWidget.ID };
wi.WidgetReference = new EntityReference<Widget> { EntityKey = newWidget.EntityKey };
But no luck. The entity does not update. There’s no exception either. So, I
had to go for a totally different approach for updating entities.
public TEntity Update<TEntity>(TEntity entity)
where TEntity : EntityObject
{
AttachUpdated(entity);
this.SaveChanges(true);
return entity;
}
public void AttachUpdated(EntityObject objectDetached)
{
if (objectDetached.EntityState == EntityState.Detached)
{
object currentEntityInDb = null;
if (this.TryGetObjectByKey(objectDetached.EntityKey, out currentEntityInDb))
{
this.ApplyPropertyChanges(objectDetached.EntityKey.EntitySetName, objectDetached);
ApplyReferencePropertyChanges((IEntityWithRelationships)objectDetached,
(IEntityWithRelationships)currentEntityInDb);
}
else
{
throw new ObjectNotFoundException();
}
}
}
public void ApplyReferencePropertyChanges(
IEntityWithRelationships newEntity,
IEntityWithRelationships oldEntity)
{
foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
{
var oldRef = relatedEnd as EntityReference;
if (oldRef != null)
{
var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
oldRef.EntityKey = newRef.EntityKey;
}
}
}
With this code, and doing the WidgetReference
thing, the test passes.
Remember, with this update code and not changing the WidgetReference
would result in an exception:
failed: System.Data.UpdateException : A relationship is being added or deleted from an AssociationSet 'FK_WidgetInstance_Widget'. With cardinality constraints, a corresponding 'WidgetInstance' must also be added or deleted.
at System.Data.Mapping.Update.Internal.UpdateTranslator.RelationshipConstraintValidator.ValidateConstraints()
at System.Data.Mapping.Update.Internal.UpdateTranslator.ProduceCommands()
at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter)
at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache)
at System.Data.Objects.ObjectContext.SaveChanges(Boolean acceptChangesDuringSave)
DropthingsEntities2.cs(278,0): at EntityFrameworkTest.DropthingsEntities2.Update[TEntity](TEntity entity)
Program.cs(646,0): at EntityFrameworkTest.Program.Changing_referenced_entity_should_work_just_like_updating_regular_entity()
So, you have to use this new Update<>
method, as well as do the
WidgetReference
trick.
Deleting connected and disconnected entities
Even a simple delete does not work as you expect in EF. You have to handle
several scenarios to make it work properly in common use cases. First is the
disconnected delete. Say you loaded an entity from one context and then you want
to delete it from another context. Something like this:
[Fact]
public void Should_be_able_to_delete_entity_loaded_from_another_context()
{
var wi = default(WidgetInstance);
using (var context = new DropthingsEntities2())
{
wi = context.WidgetInstance.Include("WidgetZone").Include("Widget").OrderByDescending(w => w.Id).First();
}
using (var context = new DropthingsEntities2())
{
context.Delete<WidgetInstance>(wi);
var deletedWi = getWidgetInstance(context, wi.Id).FirstOrDefault();
Assert.Null(deletedWi);
}
}
This is the most common disconnected delete scenario in n-tier apps. In order
to make this work, you need to make a custom Delete function.
public void Delete<TEntity>(TEntity entity)
where TEntity : EntityObject
{
this.Attach<TEntity>(entity);
this.Refresh(RefreshMode.StoreWins, entity);
this.DeleteObject(entity);
this.SaveChanges(true);
}
This will handle disconnected delete scenario. But it will fail if you load
an entity and then try to delete it on the same context. For that, you need to
add some extra check.
public void Delete<TEntity>(TEntity entity)
where TEntity : EntityObject
{
if (entity.EntityState != EntityState.Detached)
this.Detach(entity);
if (entity.EntityKey != null)
{
var onlyEntity = default(object);
if (this.TryGetObjectByKey(entity.EntityKey, out onlyEntity))
{
this.DeleteObject(onlyEntity);
this.SaveChanges(true);
}
}
else
{
this.Attach<TEntity>(entity);
this.Refresh(RefreshMode.StoreWins, entity);
this.DeleteObject(entity);
this.SaveChanges(true);
}
}
This will satisfy the following test:
[Fact]
public void Should_be_able_to_delete_entity_loaded_from_same_context()
{
var wi = default(WidgetInstance);
using (var context = new DropthingsEntities2())
{
wi = context.WidgetInstance.Include("WidgetZone").Include("Widget").OrderByDescending(w => w.Id).First();
context.Delete<WidgetInstance>(wi);
var deletedWi = getWidgetInstance(context, wi.Id).FirstOrDefault();
Assert.Null(deletedWi);
}
}
Thus you can delete entities which aren’t disconnected and has a valid
EntityKey
as well as delete entities which are disconnected. Moreover, you can
delete entities using stubs only. Like this:
[Fact]
public void Should_be_able_to_delete_entity_using_stub()
{
var wi = default(WidgetInstance);
using (var context = new DropthingsEntities2())
{
wi = context.WidgetInstance.Include("WidgetZone").Include("Widget").OrderByDescending(w => w.Id).First(); ;
context.Delete<WidgetInstance>(new WidgetInstance { Id = wi.Id });
var deletedWi = getWidgetInstance(context, wi.Id).FirstOrDefault();
Assert.Null(deletedWi);
}
}
This works fine as well.
100% Unit testable ObjectContext
Making DataContext
in Linq to SQL unit testable was hard. You had to extend
the generated DataContext
and add a lot of stuff. EF is not easier in anyway.
Here’s how I made a fully unit testable ObjectContext
, which contains all the
code I have shown above and some more – the complete class.
public class DropthingsEntities2 : DropthingsEntities, IDatabase
{
private static Dictionary<string, Action<DropthingsEntities2, object>>
_AddToMethodCache =
new Dictionary<string, Action<DropthingsEntities2, object>>();
private static Dictionary<string, Action<DropthingsEntities2, object>>
_AttachMethodCache =
new Dictionary<string, Action<DropthingsEntities2, object>>();
public DropthingsEntities2() : base()
{
this.aspnet_Applications.MergeOption =
this.aspnet_Membership.MergeOption =
this.Widget.MergeOption =
...
...
this.WidgetZone.MergeOption = System.Data.Objects.MergeOption.NoTracking;
if (_AddToMethodCache.Count == 0)
{
lock (_AddToMethodCache)
{
if (_AddToMethodCache.Count == 0)
{
_AddToMethodCache.Add(typeof(aspnet_Applications).Name,
(context, entity) => context.AddToaspnet_Applications(entity as aspnet_Applications));
_AddToMethodCache.Add(typeof(aspnet_Membership).Name,
(context, entity) => context.AddToaspnet_Membership(entity as aspnet_Membership));
...
...
}
}
}
if (_AttachMethodCache.Count == 0)
{
lock (_AttachMethodCache)
{
if (_AttachMethodCache.Count == 0)
{
_AttachMethodCache.Add(typeof(aspnet_Applications).Name,
(context, entity) => context.AttachTo("aspnet_Applications", entity));
_AttachMethodCache.Add(typeof(aspnet_Membership).Name,
(context, entity) => context.AttachTo("aspnet_Membership", entity));
...
...
}
}
}
}
public IQueryable<TReturnType> Query<TReturnType>(Func<DropthingsEntities, IQueryable<TReturnType>> query)
{
return query(this);
}
public IQueryable<TReturnType> Query<Arg0, TReturnType>(Func<DropthingsEntities, Arg0, IQueryable<TReturnType>> query, Arg0 arg0)
{
return query(this, arg0);
}
public IQueryable<TReturnType> Query<Arg0, Arg1, TReturnType>(Func<DropthingsEntities, Arg0, Arg1, IQueryable<TReturnType>> query, Arg0 arg0, Arg1 arg1)
{
return query(this, arg0, arg1);
}
public IQueryable<TReturnType> Query<Arg0, Arg1, Arg2, TReturnType>(Func<DropthingsEntities, Arg0, Arg1, Arg2, IQueryable<TReturnType>> query, Arg0 arg0, Arg1 arg1, Arg2 arg2)
{
return query(this, arg0, arg1, arg2);
}
public TEntity Insert<TEntity>(TEntity entity)
where TEntity : EntityObject
{
AddTo<TEntity>(entity);
this.SaveChanges(true);
this.Detach(entity);
return entity;
}
public TEntity Insert<TParent, TEntity>(
TParent parent,
Action<TParent, TEntity> addChildToParent,
TEntity entity)
where TEntity : EntityObject
where TParent : EntityObject
{
AddTo<TParent, TEntity>(parent, addChildToParent, entity);
this.SaveChanges();
this.AcceptAllChanges();
this.Detach(parent);
this.Detach(entity);
return entity;
}
public TEntity Insert<TParent1, TParent2, TEntity>(
TParent1 parent1, TParent2 parent2,
Action<TParent1, TEntity> addChildToParent1,
Action<TParent2, TEntity> addChildToParent2,
TEntity entity)
where TEntity : EntityObject
where TParent1 : EntityObject
where TParent2 : EntityObject
{
AddTo<TParent1, TParent2, TEntity>(parent1, parent2, addChildToParent1, addChildToParent2, entity);
this.SaveChanges(true);
this.Detach(parent1);
this.Detach(parent2);
this.Detach(entity);
return entity;
}
public void InsertList<TEntity>(IEnumerable<TEntity> entities)
where TEntity : EntityObject
{
entities.Each(entity => Attach<TEntity>(entity));
this.SaveChanges(true);
}
public void InsertList<TParent, TEntity>(
TParent parent,
Action<TParent, TEntity> addChildToParent,
IEnumerable<TEntity> entities)
where TEntity : EntityObject
where TParent : EntityObject
{
entities.Each(entity => AddTo<TParent, TEntity>(parent, addChildToParent, entity));
this.SaveChanges(true);
}
public void InsertList<TParent1, TParent2, TEntity>(
TParent1 parent1, TParent2 parent2,
Action<TParent1, TEntity> addChildToParent1,
Action<TParent2, TEntity> addChildToParent2,
IEnumerable<TEntity> entities)
where TEntity : EntityObject
where TParent1 : EntityObject
where TParent2 : EntityObject
{
entities.Each(entity => AddTo<TParent1, TParent2, TEntity>(parent1, parent2,
addChildToParent1, addChildToParent2, entity));
this.SaveChanges();
this.AcceptAllChanges();
}
private void AddTo<TParent, TEntity>(TParent parent,
Action<TParent, TEntity> addChildToParent, TEntity entity)
where TEntity : EntityObject
where TParent : EntityObject
{
Attach<TParent>(parent);
addChildToParent(parent, entity);
}
private void AddTo<TParent1, TParent2, TEntity>(TParent1 parent1,
TParent2 parent2, Action<TParent1, TEntity> addChildToParent1,
Action<TParent2, TEntity> addChildToParent2, TEntity entity)
where TEntity : EntityObject
where TParent1 : EntityObject
where TParent2 : EntityObject
{
Attach<TParent1>(parent1);
Attach<TParent2>(parent2);
addChildToParent1(parent1, entity);
addChildToParent2(parent2, entity);
}
public void Attach<TEntity>(TEntity entity)
where TEntity : EntityObject
{
if (entity.EntityState != EntityState.Detached)
return;
ObjectStateEntry entry;
if (ObjectStateManager.TryGetObjectStateEntry(entity, out entry))
{
}
else
{
_AttachMethodCache[typeof(TEntity).Name](this, entity);
}
}
public void AddTo<TEntity>(TEntity entity)
{
_AddToMethodCache[typeof(TEntity).Name](this, entity);
}
public TEntity Update<TEntity>(TEntity entity)
where TEntity : EntityObject
{
AttachUpdated(entity);
this.SaveChanges(true);
return entity;
}
public void UpdateList<TEntity>(IEnumerable<TEntity> entities)
where TEntity : EntityObject
{
foreach (TEntity entity in entities)
{
Attach<TEntity>(entity);
SetEntryModified(this, entity);
}
this.SaveChanges(true);
}
public void AttachUpdated(EntityObject objectDetached)
{
if (objectDetached.EntityState == EntityState.Detached)
{
object currentEntityInDb = null;
if (this.TryGetObjectByKey(objectDetached.EntityKey, out currentEntityInDb))
{
this.ApplyPropertyChanges(objectDetached.EntityKey.EntitySetName, objectDetached);
ApplyReferencePropertyChanges((IEntityWithRelationships)objectDetached,
(IEntityWithRelationships)currentEntityInDb);
}
else
{
throw new ObjectNotFoundException();
}
}
}
public void ApplyReferencePropertyChanges(
IEntityWithRelationships newEntity,
IEntityWithRelationships oldEntity)
{
foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
{
var oldRef = relatedEnd as EntityReference;
if (oldRef != null)
{
var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
oldRef.EntityKey = newRef.EntityKey;
}
}
}
public void Delete<TEntity>(TEntity entity)
where TEntity : EntityObject
{
if (entity.EntityState != EntityState.Detached)
this.Detach(entity);
if (entity.EntityKey != null)
{
var onlyEntity = default(object);
if (this.TryGetObjectByKey(entity.EntityKey, out onlyEntity))
{
this.DeleteObject(onlyEntity);
this.SaveChanges(true);
}
}
else
{
this.Attach<TEntity>(entity);
this.Refresh(RefreshMode.StoreWins, entity);
this.DeleteObject(entity);
this.SaveChanges(true);
}
}
}
}
You can take this class as it is. The only thing you need to change is inside
the constructor set all the entities to MergeOption = NoTracking
. Then you need
to create the AttachTo
and AddTo
mapping for every entity you have in your
ObjectContext
. This is the only hard part.
Here’s an example of unit testing a repository which is using the
extended ObjectContext
:
[Specification]
public void GetPage_Should_Return_A_Page_from_database_when_cache_is_empty_and_then_caches_it()
{
var cache = new Mock<ICache>();
var database = new Mock<IDatabase>();
IPageRepository pageRepository = new PageRepository(database.Object, cache.Object);
const int pageId = 1;
var page = default(Page);
var samplePage = new Page() { ID = pageId, Title = "Test Page", ColumnCount = 3, LayoutType = 3, VersionNo = 1, PageType = (int)Enumerations.PageTypeEnum.PersonalPage, CreatedDate = DateTime.Now };
database
.Expect<IQueryable<Page>>(d => d.Query<int, Page>(CompiledQueries.PageQueries.GetPageById, 1))
.Returns(new Page[] { samplePage }.AsQueryable()).Verifiable();
"Given PageRepository and empty cache".Context(() =>
{
cache.Expect(c => c.Get(It.IsAny<string>())).Returns(default(object));
cache.Expect(c => c.Add(It.Is<string>(cacheKey => cacheKey == CacheKeys.PageKeys.PageId(pageId)),
It.Is<Page>(cachePage => object.ReferenceEquals(cachePage, samplePage)))).Verifiable();
});
"when GetPageById is called".Do(() =>
page = pageRepository.GetPageById(1));
"it checks in the cache first and finds nothing and then caches it".Assert(() =>
cache.VerifyAll());
"it loads the page from database".Assert(() =>
database.VerifyAll());
"it returns the page as expected".Assert(() =>
{
Assert.Equal<int>(pageId, page.ID);
});
}
As you can see I can stub out all Query calls using Moq. Similarly I can stub out Insert,
Update, Delete as well and thus produce unit test for the repositories.
Conclusion
Entity Framework is hard to use in a disconnected environment. Microsoft did
not make it any easier than Linq to SQL. Just like Linq to SQL, it requires a
lot of hack to make it work properly in an environment where database operations
happen on a mix of connected and disconnected entities. Since you have to do
hack on Linq to SQL anyways, I recommend going towards Entity Framework at the
expense of these new hacks and fundamentally different way of dealing with
entities, since most of the innovations are happing on the EF area nowadays.