Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / C#

Finally! Entity Framework working in fully disconnected N-tier web app

Rate me:
Please Sign up or sign in to vote.
4.91/5 (40 votes)
11 Jun 2011CPOL12 min read 213.2K   2.9K   172   31
Entity Framework is world’s most difficult ORM for n-tier application. See how I have produced a 100% unit testable fully n-tier compliant data access layer following the repository pattern using Entity Framework.

Download EntityFrameworkTest.zip - 3.57 KB - sample 100% Unit Testable ObjectContext  

Introduction 

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. 

Extending the generated ObjectContext

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: 

C#
public class DropthingsDataContext2 : DropthingsDataContext, IDatabase 
{ 

The original ObjectContext is called DropthingsDataContext. Throughout the code you will use this extended XXXX2 class than the original one.

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:

C#
public DropthingsDataContext2(string connectionString) : base(connectionString) 
{ 
	this.aspnet_Memberships.MergeOption = 
	this.aspnet_Profiles.MergeOption = 
	this.aspnet_Roles.MergeOption = 
	this.aspnet_Users.MergeOption = 
	this.Columns.MergeOption = 
	this.Pages.MergeOption = 
	this.RoleTemplates.MergeOption = 
	this.Tokens.MergeOption = 
	this.UserSettings.MergeOption = 
	this.Widgets.MergeOption = 
	this.WidgetInstances.MergeOption = 
	this.WidgetsInRolesSet.MergeOption = 
	this.WidgetZones.MergeOption = System.Data.Objects.MergeOption.NoTracking; 

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.

Next I create a static dictionary which contains the Attach and AddTo calls for each and every entity.

C#
private static Dictionary<string, Action<DropthingsDataContext, object>> 
    _AddToMethodCache = 
        new Dictionary<string, Action<DropthingsDataContext, object>>();

private static Dictionary<string, Action<DropthingsDataContext, object>> 
    _AttachMethodCache = 
        new Dictionary<string, Action<DropthingsDataContext, object>>();

public DropthingsDataContext2(string connectionString) : base(connectionString) 
{ 
    this.aspnet_Memberships.MergeOption = 
    ... 
    this.WidgetZones.MergeOption = System.Data.Objects.MergeOption.NoTracking;

    if (_AddToMethodCache.Count == 0) 
    { 
        lock (_AddToMethodCache) 
        { 
            if (_AddToMethodCache.Count == 0) 
            { 
                _AddToMethodCache.Add(typeof(aspnet_Membership).Name, 
                    (context, entity) => context.AddToaspnet_Memberships(entity as aspnet_Membership)); 
                _AddToMethodCache.Add(typeof(aspnet_Profile).Name, 
                    (context, entity) => context.AddToaspnet_Profiles(entity as aspnet_Profile)); 
                ... 
            } 
        } 
    }

    if (_AttachMethodCache.Count == 0) 
    { 
        lock (_AttachMethodCache) 
        { 
            if (_AttachMethodCache.Count == 0) 
            { 
                _AttachMethodCache.Add(typeof(aspnet_Membership).Name, 
                    (context, entity) => context.AttachTo("aspnet_Memberships", entity)); 
                _AttachMethodCache.Add(typeof(aspnet_Profile).Name, 
                    (context, entity) => context.AttachTo("aspnet_Profiles", entity)); 
                ... 
            } 
        } 
    } 
}
These are used to Attach and Add entities to entity sets in a generic function.

C#
public void Attach<TEntity>(TEntity entity) 
    where TEntity : EntityObject 
{ 
    if (entity.EntityState != EntityState.Detached) 
        return; 
    // Let's see if the same entity with same key values are already there 
    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:

C#
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 

Widget_WidgetInstance.png

In EF, there’s no join. You can directly access a referenced entit:

C#
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.

C#
from widgetInstance in dc.WidgetInstance
where widgetInstance.Id == widgetInstanceId
select widgetInstance.WidgetZone.Column.FirstOrDefault().Page 

The entity model is like this: 

WidgetZone_Column_Page.png

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

Ancestor_Objects_Property.png

For this the query in EF is like this: 

C#
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. 

C#
public TEntity Insert<TEntity>(TEntity entity) 
            where TEntity : EntityObject 
{ 
    AddTo<TEntity>(entity); 
    this.SaveChanges(true); 
    // Without this, attaching new entity of same type in same context fails. 
    this.Detach(entity); 
    return entity; 
} 

Here’s the small test that shows it works:

C#
[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:

C#
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();
    // Without this, consequtive insert using same parent in same context fails.
    this.Detach(parent); 
    // Without this, attaching new entity of same type in same context fails.
    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:

C#
[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:

Many_to_many_entities.png

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. 

C#
[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:

C#
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);

    // Without this, consecutive insert using same parent in same context fails.
    this.Detach(parent1);
    // Without this, consecutive insert using same parent in same context fails.
    this.Detach(parent2);
    // Without this, attaching new entity of same type in same context fails.
    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);

    //AddTo<TEntity>(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:

C#
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:

C#
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.

C#
[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.

C++
[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: 

C#
[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();

        // Load the related entities separately so that they get into ObjectStateManager 
        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); 
    }

    // Update the same entity again in a different ObejctContext. Simulating 
    // the scenario where the entity was stored in a cache and now retrieved 
    // from cache and being updated in a separate thread. 
    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:

C#
[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:

C#
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.

C#
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:

C#
[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.

C#
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.

C#
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:

C#
[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:

C#
[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.

C#
    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);
            // Without this, attaching new entity of same type in same context fails.
            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();
            // Without this, consequtive insert using same parent in same context fails.
            this.Detach(parent); 
            // Without this, attaching new entity of same type in same context fails.
            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);

            // Without this, consequtive insert using same parent in same context fails.
            this.Detach(parent1);
            // Without this, consequtive insert using same parent in same context fails.
            this.Detach(parent2);
            // Without this, attaching new entity of same type in same context fails.
            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);            
            //AddTo<TEntity>(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);

            //AddTo<TEntity>(entity);
        }

        public void Attach<TEntity>(TEntity entity)
            where TEntity : EntityObject
        {
            if (entity.EntityState != EntityState.Detached)
                return;
            // Let's see if the same entity with same key values are already there
            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 is empty
        cache.Expect(c => c.Get(It.IsAny<string>())).Returns(default(object));
        // It will cache the Page object afte loading from database
        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. 

Image 5

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect BT, UK (ex British Telecom)
United Kingdom United Kingdom

Comments and Discussions

 
GeneralMy vote of 5 Pin
Sasha Laurel19-Jul-12 4:06
Sasha Laurel19-Jul-12 4:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.