1

I have the Profile model listed below. This model is sent to the client via an ajax call and then updated and sent back to the server via another ajax call.

When the client makes changes they are responsible for setting the ObjectState of the model they are working with. For example if they were changing the Name property they would set the Name property to the new value and also set the ObjectState property to 2 (Modified) and to add a new Address to the Addresses they would set the ObjectState of the new Address to 1 (Added). This all works great and the ApiController receives the new Profile object properly.

public enum ObjectState
{
    Unchanged = 0,
    Added = 1,
    Modified = 2,
    Deleted = 3
}

public interface IObjectState
{
    ObjectState ObjectState { get; set; }
}

static class EntityStateHelper
{
    public static EntityState ConvertState(ObjectState objectState)
    {
        switch (objectState)
        {
            case ObjectState.Added:
                return EntityState.Added;
            case ObjectState.Deleted:
                return EntityState.Deleted;
            case ObjectState.Modified:
                return EntityState.Modified;
            default:
                return EntityState.Unchanged;
        }
    }
}

// Models
public class Profile : IObjectState
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime? BirthDay { get; set; }

    // Navigation Properties
    public ICollection<Address> Addresses { get; set; }

    public ObjectState ObjectState { get; set; }
}

public class Address : IObjectState
{
    public int Id { get; set; }
    public string Street1 { get; set; }
    public string Street2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
    public ObjectState ObjectState { get; set; }
}

Inside of my ProfileRepository I have actually have separated out Insert and Update and in the case of the Profile object, the client does not have a service call to delete a profile so really all they can do for a profile is modify it. In my Update method I choose to go with _context.Entry<T>(entity) as apposed to _context.Entry<T>(entity).State = EntityState.Modified cause this throw the exception stating An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

public void Update(T entity)
{
    _context.Entry<T>(entity);
    _context.ApplyStateChanges();
}

First let me say that I don't actually have ApplyStateChanges in the Update method but for this sample it will show what is happening. Below is the code for the ApplyStateChanges method.

public static class DbContextEntensions
{
    public static void ApplyStateChanges(this DbContext context)
    {
        foreach (var trackableEntry in context.ChangeTracker.Entries<IObjectState>())
        {
            IObjectState state = trackableEntry.Entity;
            trackableEntry.State = EntityStateHelper.ConvertState(state.ObjectState);
        }
    }
} 

So here is the issue. Let's say that the Profile at the client is that the Name property is John and the Addresses property contains 1 address. If the client changes the Name to Steve and modifies the 1 Address all is good cause when ApplyStateChanges get called the DbEntityEntries for each get marked with the proper EntityState. The problem comes about when the client adds a new Address to the Addresses collection with a State of 1 (Added). When ApplyStateChanges gets called the call to context.ChangeTracker.Entries<IObjectState>() it does not return the newly added Address as an Entry that has changed by the ChangeTracker.

I am pretty certain that the code _context.Entry<T>(entity) should be _context.Entry<T>(entity).State = EntityState.Modified but I can't seem to get around this exception no matter what I have tried.

I am using EntityFramework 5.0 with .NET 4.0 meaning an EntityFramework version of 4.4. What am I missing? I have read what I feel is every article that I can find on the subject and this looks like it should work. I got this State change tracking code from Julie Lerman's PluralSight video Entity Framework in the Enterprise.

Any help in pointing me in the right direction would be greatly appreciated.

john.hidey
  • 11
  • 2
  • 5

2 Answers2

0

I would suggest in the Update method adding the entity using .Add(entity) this would mark the new address as Added and fixup its navigation properties. Then when you call the ApplyStateChanges it would correct the states.

Mickey Puri
  • 835
  • 9
  • 18
0

This post is old but it may help others so :

The problem is in the Update Method. I assume you are working in disconnected mode. The below method :

_context.Entry<T>(entity);

get the entity from the context.
If it's not in it, it will add it (and its navigation properties as Address) with the "Detached" EntityState, **which won't add it to the "context.ChangeTracker.Entries()" **.

you should use the ".Add(entity)" or the ".Attach(entity)" instead. The only difference is the states :

  • .Add will add the Customer and its Address with EntityState : "Added"

  • .Attach will add the Customer and its Address with EntityState : "Unchanged"

Both will work in your code as you change states (again!) of them after in the line below :

_context.ApplyStateChanges();

This means you don't care of the state you apply before, as long as they are tracked (not in detach state).

I am pretty certain that the code _context.Entry(entity) should be _context.Entry(entity).State = EntityState.Modified but I can't seem to get around this exception no matter what I have tried.

If you don't do the ApplyStateChanges() after, its updating the entity and its whole graph is mark as dirty making an unexpected result (surely like adding its graph elements. Check this answer with the whole explanation).

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

This means you already have the same object (or more surely one of its navigational property) in your database/test. This has nothing to do with your 'real' problem and is more likely a side effect. Check in the Seed method of your database, you must have a duplicate navigational entity.

Community
  • 1
  • 1