1

I have added a row to my database and come back with a different context to update it. I have this class:

public abstract partial class DataManager<I, C> 
    where C : class, IDomainObject, I, new( ) where I : IDomainObject

C might be an EntityObject, but this class doesn't know that.

My Save looks like this:

    public virtual bool Save( I _item )
    {
        bool rc = true;
        try
        {
        var set = m_Context.GetObjectSet<I, C>( );
        ObjectStateEntry stateEntry = null;
        if( ! m_Context.ObjectStateManager.TryGetObjectStateEntry( ( C ) _item, out stateEntry ) )
        {
            if( _item is EntityObject )
            {
                    if ( _item.IsNew )
                    {
                        set.AddObject( ( C ) _item );
                    }
                    else
                    {
                        try
                        {
                            set.Attach( ( C ) _item );
                        }
                        catch( Exception ex )
                        {
                            set.ApplyCurrentValues( ( C ) _item );
                        }

and so on...

In my test case, stateEntry is not found by TryGetObjectStateEntry. It is, however, an EntityObject, and it is not new (IsNew is my flag), so it gets to the else. Here's my problem: set.Attach throws this error

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

and in the very next instant, ApplyCurrentValues throws this one:

"An object with a key that matches the key of the supplied object could not be found in the ObjectStateManager. Verify that the key values of the supplied object match the key values of the object to which changes must be applied."

How can these both be true?

MORE INFORMATION:

_item was created by a Get in another context. That context was then disposed. At that point, _item had an EntityState.Unchanged. I applied some changes to it, and it changed to EntityState.Modified. (I didn't expect that, since the context (and its ObjectStateManager) should have been gone.) At any rate, once it gets to Save (above), it's state (as reported by the debugger) is Modified, but I have a new context by then. If I get a list of ALL the ObjectStateEntries (Added, Deleted, Modified, Unchanged) at this point, there are only two, and _item is not one of them, as ApplyCurrentValues reports, but it can't be Attached because "it is too there!". Perhaps the problem is that it is still attached to an old ObjectStateManager (could there some reference which won't let the ObjectStateManager dispose?).

Danny Varod
  • 15,783
  • 5
  • 58
  • 98
Kelly Cline
  • 2,076
  • 2
  • 31
  • 54
  • Are you calling context.SaveChanges() twice? – Danny Varod Jun 29 '12 at 20:51
  • @DannyVarod - I am using UnitOfWork, in which I only call SaveChanges when I dispose the Unit, and I dispose of the Context at the same time. This is a web app, so it has very short life-cycles for the Context. The WcfService has used up a UnitOfWork to get _item (that "Get in another context" that I mentioned). But once we get to Save, there is no opportunity for SaveChanges to get called twice. – Kelly Cline Jun 29 '12 at 21:19
  • And are you sure that you aren't getting the same Uow twice from your dependency injection framework? – Danny Varod Jun 29 '12 at 21:23
  • @DannyVarod - I'm pretty sure. I write out the Context HashCode every time I instantiate a Uow and every time I call Save. I got a different number for the context that loaded the row from the context that attempts to save it... But, let's say I'm missing something - Maybe you are onto something: How would using the same Uow or calling SaveChanges twice cause ApplyCurrentValues and Attach to disagree about whether an object is in the ObjectStateManager or not? Keep in mind that these two "different" errors are back-to-back with no intervening code (of mine). – Kelly Cline Jun 29 '12 at 21:53
  • I haven't seen your Uow code, however, with EF you can call SaveChanges() a few times prior to calling AcceptChanges(). Could one of your other saved entities be holding a reference to the double saved object? – Danny Varod Jun 29 '12 at 21:57
  • @DannyVarod - one more bit of information may be helpful. The _item is a User, which has a ParentId, which is a foreign key back to the same (Users) table. It so happens that the item to which that foreign key points is, in fact, in the context (it is the Parent User who is editing the User, which is _item). Is it perhaps this object to which the failing Attach refers as "already exists in Object State Manager"? – Kelly Cline Jul 02 '12 at 04:19
  • In that case, attach the parent and add children to it. Don't attach the child too. – Danny Varod Jul 02 '12 at 10:40
  • @DannyVarod - (I was working from memory on that last comment and made a mistake). My _item is a User, which has a nav property, Organization. In my context is another User (not Parent) who happens to have the same Organization. The error in Attach is that these two Organization objects have the same key, but are different objects. In debug, if I null out the _item.Organization, the Attach works. What I don't understand is how I would know to do this, since _item has no clue that other user is in the context and, although other user has a ref to Org, the Org object is not actually in OSM. – Kelly Cline Jul 02 '12 at 15:18
  • I have Julie Lerman's book, but apparently I still haven't got a handle on how to manage associations. The fact that the "other user" is in my context at all is, in this case, a design flaw, so when I fix it, this particular problem will be swept under the rug, but I won't have learned what I set out to here. – Kelly Cline Jul 02 '12 at 15:20
  • So you are attaching two different users with the same org, however, the org object is a different instance in each? – Danny Varod Jul 02 '12 at 16:34
  • @DannyVarod - Sort of... I have Original User with an OrganizationId (the Organization is not Loaded), and I am trying to attach Second User, who has the same OrganizationId and whose Organization was loaded when it was in another context. The two Users have the same OrganizationId, but nothing else in common. By the time the error occurs, we are pretty deep in the stack trace, at ObjectContext.AttachSingleObject from RelatedEnd.AddEntityToOSM from AddGraphToOSM from `EntityCollection`.Include from RelatedEnd.WalkObjectGraph from RelatedEnd.IncludeEntity from `EntityReference`.Include. – Kelly Cline Jul 02 '12 at 19:13
  • If the organization is already in the DB, could you set the nav prop to null and only set the ID or load the organization from the DB and put it the nav props? – Danny Varod Jul 02 '12 at 19:47
  • @DannyVarod - looks like our discussion has gone too long :) Thanks. If I set the nav prop to null and only set the ID, it works. – Kelly Cline Jul 02 '12 at 19:53

2 Answers2

0

The problem is that each time the db-context is initialized, it takes the rows from the database and foreach row, assign an entity key. which means the same row, retrieved by 2 db-context will have 2 different entity keys. the entity key is like an id in entity framework and it is used instead of the ID of the entity in question. But the constraints of the tables are although preserved (no duplicate primary key) Hope this make sense.

Boomer
  • 1,440
  • 1
  • 15
  • 19
  • The answer makes sense, but the question is, how can the same object in the same context be reported by Attach as "already exists" and be reported by ApplyCurrentValues as "could not be found"? – Kelly Cline Jun 29 '12 at 19:41
  • I honestly don't know what causing the second issue. but try to solve the first one and it is maybe related to the second. I suggest (fast test) that you use a static dbcontext – Boomer Jun 29 '12 at 19:44
0

The problem is that you are attaching two different users with the same org, however, the org object is a different instance in each case.

If the organization is already in the DB, try:

  1. Either setting the navigation properties to the organization to null and only set the OrganizationId properties.

  2. Or load the organization entity from the DB and put it the nav props.

If the organization is not in the DB:

Use the same instance (throw one away and replace it with the other).

Danny Varod
  • 15,783
  • 5
  • 58
  • 98