Basic Data Operations with the Entity Framework V6 RC1

Introduction

Following on from the previous article, Data Access with the Entity Framework V6 RC1, this time I’m going to go deeper and look at how we manipulate entities through a structured and often times generic approach.

We’ll take a particularly close look at an approach to adding, updating and deleting single entities for both “connected” and “disconnected” states.

Note: the data access code has been updated since this article was first published.  Refer to this article for the latest code.

Entity States

There are two major entity states to take into account – connected and disconnected.  It’s important to look at this up front, because when you pass POCO objects between system boundaries (e.g. via WCF services) objects will frequently be disconnected from their original parent context.

Once an entity has become disconnected, it must be attached to another context for data operations to be applied successfully to them.  I’m going to focus on some admittedly simple scenarios in this article, and I’ll go deeper in the follow-up article.

Data Operations

For the purposes of this article, I’m going to keep this introduction very simple, and I’m going to focus on three basic data management actions: Add/Insert, Modify/Update and Remove/Delete.

image

We’ve covered off limited read/select functionality in the previous article.  The aim I’ll be attempting to reach is to outline basic operational support for the above functionality using the Entity Framework.

An Interface Based Approach

To ensure that data access is uniform, I’ve designed a common interface for the Data Access classes to implement, this can be extended as the model and functionality becomes more complex:

image

For each Data Access class implementing the IDataAccessor interface, there needs to be an implementation of the following functions:

  • Delete,
  • InsertOrUpdate,
  • Select, and,
  • SaveChanges

Semantically, sometimes the difference between inserting a new row and updating an existing one is fairly minor.  As a result, there’s sometimes a preference to combine the two operations into a verb action of “AddOrUpdate” or in database parlance, “Upsert”.

In an effort to reduce duplication/coding surface, I’ve merged the two actions together at the entity level, hence the “InsertOrUpdate” function.  This provides each entity accessor with essential CRUD (Create/Read/Update/Delete) functionality on a per-entity basis.

The Implementation: Add/Update

Implementing this functionality at the entity level is rather straightforward, to ensure that we invoke the right functionality, the implemented function needs to just check whether the entity’s primary key property has a value or not.  If there is no PK value, we assume that it’s an Add operation, and can just add the entity directly to the Data Context.  I’m making a check to see if the PK value is valid or not first:

public void InsertOrUpdate(Size entity)
{
    if (entity == null)
    {
        throw new ArgumentNullException("Entity must be valid");
    }

    if (DataContext.Sizes.Any(x => x.SizeId == entity.SizeId))
    {
        entity = PrepareUpdate<Size>(entity, entity.SizeId);
    }
    else
    {
        DataContext.Sizes.Add(entity);
    }
}

 

Updating a single Entity

Adding a new entity is pretty trivial, updating one is less so.  There’s two things to consider – the state of the entity!

To make this process a whole lot simpler for calling classes, I’ve moved the implementation into the base class and made it generic.  The logic that is context-specific still lives in the entity-specific data access class though.

Here’s the implementation in the base class:

protected T PrepareUpdate<T>(T entity, int key) where T : EntityBase 
{        
    if (entity == null) 
    {
        throw new ArgumentException("Cannot add or update a null entity.");
    }            
    var entry = DataContext.Entry<T>(entity);
    if (entry.State == EntityState.Detached)
    {
        var set = DataContext.Set<T>();
        T attachedEntity = set.Find(key);  // You need to have access to key

        if (attachedEntity != null)
        {
            var attachedEntry = DataContext.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
            return attachedEntity;
        }
        else
        {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
    return entity;
}

 

Obviously there’s a lot to take in here.  Let’s go through it from top to bottom.

There’s the obvious check that the entity is not null (common sense, although you hope the derived class can make this check), then we move on to obtain the entity’s data context entry, which basically establishes if the entity is attached to the current data context or not.

If the entity is in a state other than detached, then when the data context’s SaveChanges() function is called, the update (or whatever pending state) will occur as set.  If the entity is detached, then the change tracker won’t know about what updates have occurred to the entity.  The next logical step therefore is to ensure that the detached entity is now attached to the current data context.

We need to find the entity by it’s key (typically a PK property), I’m assuming each entity has an integer PK property – which my data model actually supports be design.  You might find your data model differs, e.g. you might have GUID based PK values, or no PK columns at all (although the Entity Framework doesn’t handle tables with no primary key very well).

If there’s already an instance of the entity loaded and connected to the current data context, we load it and apply the changes from the entity being passed in.  If there’s no current object, we set the entity’s state to “Modified”, which should attach the entity.

To use this functionality, it is as simple as the following test case:

public void TryAddDelete()
{
    using (SizeDataAccessor a = new SizeDataAccessor())
    {
        Size obj = new Size();
        obj.Height = 0;
        obj.Width = 0;
        obj.SizeId = 100; //out of the standard range
        obj.IsCustom = false;
        obj.Description = "Unit Test";
        obj.Dimensions = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss");

        a.InsertOrUpdate(obj);
        a.SaveChanges();     }
}

 

Deleting a single Entity

Similar to updating an entity, deleting an entity depends on whether the entity object has been attached – and in this case, there’s one tiny complication which makes it a little less obvious how we can remove an entity.  Here’s the implementation from an entity-specific data access implementation:

public void Delete(Size entity)
{
    if (entity == null)
    {
        throw new ArgumentNullException("Entity must be valid");
    }

    if (DataContext.Sizes.Any(x => x.SizeId == entity.SizeId))
    {   
        entity = PrepareDelete<Size>(entity, entity.SizeId);
        DataContext.Sizes.Remove(entity);
    }
}

 

The key to this implementation is obviously buried into the base class, for uniform access and support to all concrete data access classes:

protected T PrepareDelete<T>(T entity, int key) where T : EntityBase
{
    if (entity == null)
    {
        throw new ArgumentException("Cannot delete a null entity.");
    }
    var entry = DataContext.Entry<T>(entity);
    if (entry.State == EntityState.Detached)
    {
        var set = DataContext.Set<T>();
        T attachedEntity = set.Find(key);
        if (attachedEntity != null)
        {
            var attachedEntry = DataContext.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
            return attachedEntity;
        }
        else
        {
            entry.State = EntityState.Deleted;                   
        }
    }
    return entity;
}

 

The specific function is called “PrepareDelete”, as the intention is that it prepares the entity for removal.  Again, as per updates, the steps to perform depend on the entity’s state.  We’ll look more closely at this:

Firstly, a check is made to ensure the entity is valid, then, as with updates, if the entity is detached we need to ensure there’s no current entity attached.  If there is a currently attached version of the entity (matched by the entity key) then we return the attached entity.

If there’s no attached entity, we set the entity’s state to deleted, which will attach the entity to the current data context.

The remove operation can then be performed on the entity which is returned from the PrepareDelete function, whether it is the original entity passed in, or the one returned from the change tracker/data context.  To ensure this works, here’s a quick unit test:

public void TryAddQueryDelete()
{
    using (SizeDataAccessor a = new SizeDataAccessor())
    {
        Size obj = new Size();
        obj.Height = 0;
        obj.Width = 0;
        obj.SizeId = 100; //out of the standard range
        obj.IsCustom = false;
        obj.Description = "Unit Test";
        obj.Dimensions = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss");
        a.InsertOrUpdate(obj);
        a.SaveChanges();
    } //context disposed
            
    using (SizeDataAccessor a = new SizeDataAccessor())
    {
        Size entry = a.CreateQuery<Size>().Where(x => x.SizeId == 100)
                                          .FirstOrDefault(); Assert.IsTrue(entry != null, "Should find the new record"); Assert.IsTrue(entry.SizeId == 100,
                    "Should find the new record by SizeId == 100"); a.Delete(entry); //entity is attached a.SaveChanges(); } }

 

 

Committing Changes

Once you have finalized insert/update/delete operations against entities, you’ll want to commit them.  The base data access class provides an implementation which can be overridden (if necessary):

public virtual void SaveChanges()
{
    DataContext.SaveChanges();
}

 

This virtual function happily satisfies the IDataAccessor interface’s SaveChanges() function.  When this is called, any pending changes within the current Data Context are committed.

We’ll need to take a look at an approach for data access concurrency in our next article.

Summary

Again, a fair amount of functionality exposed by our Data Access assembly.  We’re still in the early stages, there’s a lot more to be done!  In the next article, I’ll look at concurrency patterns, bearing in mind that the premise is exposing data access and entity objects to stateless web services!

We’ll also take a look at bulk operations and exception/error handling.  There’s much more to come, although I need some more time to flesh the design out further!

Helpful Link(s)

http://stackoverflow.com/questions/12585664/an-object-with-the-same-key-already-exists-in-the-objectstatemanager-the-object

Leave a comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.