Reputation: 78
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
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
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