Ross
Ross

Reputation: 78

Error:An entity object cannot be referenced by multiple instances of IEntityChangeTracker

So, I am just about at my wits end here. I am an experienced programmer and SQL user but new to Entity Framework. (Using VS Pro 2015, EF 6)

This is my first post here, forgive me if I don't have all the protocols down yet.

I am getting the error “An entity object cannot be referenced by multiple instances of IEntityChangeTracker” when trying to attach an entity object to a Database context. I understand what the error means: My object is already attached to a context. What I don’t understand is why/how my object is still attached to a database context.

The object in question is fetched via a method call on an owning object:

ActiveWorkSession = tcUser.getActiveWorkSession();

Here is the getActiveWorkSession method:

public EmployeeWorkSession getActiveWorkSession()
{
    EmployeeWorkSession activeWS = null;

    using (PHSRP_DashboardDataModel _DBC = new PHSRP_DashboardDataModel())
    {
        var ews = _DBC.EmployeeWorkSessions
                      .Where(w => (w.EmployeeID == this.EmployeeRecord.EmployeeID) &&
                                  (w.WorkEndDateTime_Actual == null))
                      .Include(w => w.WorkStartRecords)
                      .Include(w => w.WorkEndRecords)
                      .Include(w => w.WorkSessionBreaks);

        if (ews.Count() > 0)         
        {
            activeWS = ews.First();
            if (ews.Count() > 1)
            {
                activeWS = ews.Last();
            }
        }
    }

    return activeWS;
}

You see that, in addition to being a local scope object, the DbContext (_DBC) is disposed of by the using statement before the method returns. So when later, I need to attach this ActveWorkSession to a DbContext again (since I disposed of its original context!) I first tried this:

private void btn_UndoButton_Click(object sender, EventArgs e)
{
        |
        |
         //  Various condition tests
        |
        |

    tcPunch lPunch = tcLastPunchUndo.LastPunch;

    PHSRP_DashboardDataModel _DBC = new PHSRP_DashboardDataModel();
    _DBC.EmployeeWorkSessions.Attach(ActiveWorkSession);
                          //  Blows up here   ^
    switch (lPunch.Type)
    {
        Cases with try-catch
    }
}

This gives me the "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" at the Attach statement.

Since apparently, in some cases, I was still attached to a DbContext I decided to use that context:

private void btn_UndoButton_Click(object sender, EventArgs e)
{
        |
        |
        //  Various condition tests
        |
        |

     tcPunch lPunch = tcLastPunchUndo.LastPunch;

     PHSRP_DashboardDataModel _DBC; 

     if (GetDbContextFromEntity(ActiveWorkSession)!=null)    // is ActiveWorkSession still attached to a Dbcontext ?
     {
         _DBC = (PHSRP_DashboardDataModel) GetDbContextFromEntity(ActiveWorkSession);        //  Get that Context
         // Blows up here ^
     }
     else
     {
         _DBC = new PHSRP_DashboardDataModel();        // Open new context and attach
         _DBC.EmployeeWorkSessions.Attach(ActiveWorkSession);
     }

     switch (lPunch.Type)
     {
        //  Cases with try-catch
     }
}

The GetDbContextFromEntity code is from a StackOverflow answer

This of course gives me an InvaildCastException: "Unable to cast object of type 'System.Data.Entity.DbContext' to type 'PHSRP_Dashboard.PHSRP_DashboardDataModel'." when I attempt to cast the base type of DbContext to my derived type of PHSRP_DashboardDataModel. I’m aware that that was wishful thinking on my part.

So I am looking for a solution. I’m flexible. A way to disconnect the ActiveWorkSession from its existing dbcontext, or a way to get a proper handle on that existing context, or some other option I haven’t seen yet.

My thanks.

Modifying my query (as suggested by Frank) to:

var ews = _DBC.EmployeeWorkSessions 
              .Where(w => (w.EmployeeID == this.EmployeeRecord.EmployeeID) && 
                          (w.WorkEndDateTime_Actual == null)) 
              .Include(w => w.WorkStartRecords) 
              .Include(w => w.WorkEndRecords) 
              .Include(w => w.WorkSessionBreaks) 
              .AsNoTracking(); 

did not resolve the problem- Same error

Upvotes: 0

Views: 950

Answers (2)

Ross
Ross

Reputation: 78

I found two solutions:

The first was to use the exiting DbContext without casting:

This requires additional infrastructure to use a gereral DbContext instance or a specific PHSRP_DashboardDataModel based on the entity state

I don’t like it since it does not explain or correct the underlying problem, but it works.

PHSRP_DashboardDataModel _DBC = null;
DbContext _PDBC = null;
Boolean existingContext;

if (GetDbContextFromEntity(ActiveWorkSession)!=null)    // is ActiveWorkSession still attached to a Dbcontext ?
{
    _PDBC = GetDbContextFromEntity(ActiveWorkSession);        //  Get that Context
    existingContext = true;
}
else
{
    _DBC = new PHSRP_DashboardDataModel();             // Open new Context and attach
    _DBC.EmployeeWorkSessions.Attach(ActiveWorkSession);
    existingContext = false;
}

switch (lPunch.Type)
{
     case PunchType.OutForShift:
     {
        // Remove END punch, set SessionEnd time to null 
        try
        {
            WorkEndRecord wsr = ActiveWorkSession.WorkEndRecords.First();

            if (existingContext)
            {
                ActiveWorkSession.WorkEndRecords.Remove(wsr);
                ActiveWorkSession.WorkEndDateTime_Actual = null;
                _PDBC.SaveChanges();
            }
            else
            {
               _DBC.WorkEndRecords.Remove(wsr);
               ActiveWorkSession.WorkEndDateTime_Actual = null;
              _DBC.SaveChanges();
            }

        }
        catch (Exception ex)
        {
             //  handle exception
        }

        tcUser.UpdateClockState();
        UpdateClockView();

        break;
     }

         |
         |
     Other cases
         |
         |
}

Frank Fajardo’s question sent me looking for other references to the Database Context, and ultimately the second, better solution.

The task that this code is “undoing” is a clock-out. That’s where I found my prior, still attached, reference to ActiveWorkSession.

My reference to the Database context in the SessionClockOut method was not enclosed in a “using” block. As a test, I added a detach instruction- that worked. I then placed the _DBC in a “using” block. That allowed my original btn_UndoButton_Click code to work without needing to detach the ActiveWorkSession object from the Context:

private Boolean SessionClockOut(DateTime timeOUT)
{
    Boolean success = false;
    Boolean abort = false;

    tcPunch newPunch = new tcPunch();

 // Added 'using' statement
    using (PHSRP_DashboardDataModel _DBC = new PHSRP_DashboardDataModel())
    {
        while (!success && !abort)
        {
            var wsUpdate = _DBC.EmployeeWorkSessions
                               .Where(w => (w.EmployeeWorkSessionID == ActiveWorkSession.EmployeeWorkSessionID))
                               .Include(w => w.WorkStartRecords)
                               .Include(w => w.WorkEndRecords)
                               .Include(w => w.WorkSessionBreaks)
                               .FirstOrDefault();

            if (wsUpdate != null)
            {
                WorkEndRecord er = new WorkEndRecord();

                try
                {
                    wsUpdate.WorkEndDateTime_Actual = timeOUT;

                    er.EmployeeWorkSessionID = ActiveWorkSession.EmployeeWorkSessionID;
                    er.EditTimeStamp = DateTime.Now;
                    er.WorkEndDateTime_Official = timeOUT;
                    er.VarianceReasonID = VarianceReason.VR_None;
                    er.EditByID = ActiveWorkSession.EmployeeID;

                    wsUpdate.WorkEndRecords.Add(er);
                    _DBC.SaveChanges();

                    ActiveWorkSession = wsUpdate;
                    tcUser.UpdateClockState();
                        UpdateClockView();

        //  Tried this before adding the using statement. It also worked.
        //          _DBC.Entry(ActiveWorkSession).State = EntityState.Detached;         // Release from DB Context
                    PreviousWorkSession = ActiveWorkSession;                            // needed For Undo Punch 
                    ActiveWorkSession = null;                                           // Worksession has ended
                    success = true;

                }
                catch (DbUpdateException e)
                {
                   //  handle exception...
                    abort = true;
                    throw;
                }

                newPunch.EmployeeID = wsUpdate.EmployeeID;
                newPunch.WorksessionID = wsUpdate.EmployeeWorkSessionID;
                newPunch.Type = PunchType.OutForShift;
                newPunch.TimeStamp = er.EditTimeStamp;
                if (tcUser.hasPriv(PrivilegeTokenValue.TC_UndoRecentPunch)) tcLastPunchUndo.StartUndoTimer(newPunch);
            }  // if
        }  //  while 

    }  // using

    return success;
}

Being new to Entity Framework I don't yet understand all the issues related to working with the DbContext.

Upvotes: 0

Frank Fajardo
Frank Fajardo

Reputation: 7359

You could try using AsNoTracking() within your getActiveWorkSession(). That way, your entity is not tracked.

Then when you need to change the entity, you can attach it to a new context with tracking.

Upvotes: 0

Related Questions