所以,我只是在我的智慧结束。我是一名经验丰富的程序员和SQL用户,但却是Entity Framework的新手。 (使用VS Pro 2015,EF 6)
这是我在这里发表的第一篇文章,如果我还没有完成所有协议,请原谅我。
我在尝试将实体对象附加到数据库上下文时收到错误“IEntityChangeTracker的多个实例无法引用实体对象”。我理解错误的含义:我的对象已经附加到上下文中。我不明白的是为什么/我的对象仍然附加到数据库上下文。
有问题的对象是通过对拥有对象的方法调用获取的:
ActiveWorkSession = tcUser.getActiveWorkSession();
这是getActiveWorkSession方法:
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;
}
您会看到,除了作为本地范围对象之外,DbContext(_DBC)在方法返回之前由using语句处理。 因此,稍后,我需要再次将此ActveWorkSession附加到DbContext(因为我处理了它的原始上下文!)我首先尝试了这个:
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
}
}
这给了我在Attach语句中的“一个实体对象不能被IEntityChangeTracker的多个实例引用”。
显然,在某些情况下,我仍然依附于DbContext,我决定使用该上下文:
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
}
}
GetDbContextFromEntity代码来自StackOverflow答案
这当然给了我一个InvaildCastException:“无法将'System.Data.Entity.DbContext'类型的对象强制转换为'PHSRP_Dashboard.PHSRP_DashboardDataModel'。”当我尝试将基本类型的DbContext强制转换为我的派生类型的PHSRP_DashboardDataModel时。我知道这是我的一厢情愿。
所以我正在寻找解决方案。我很灵活。一种将ActiveWorkSession与其现有dbcontext断开连接的方法,或者是对现有上下文进行正确处理的方法,或者我还没有看到的其他选项。
我的谢意。
将我的查询(由Frank建议)修改为:
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();
没有解决问题 - 同样的错误
答案 0 :(得分:0)
您可以尝试在getActiveWorkSession()
中使用AsNoTracking()。这样,您的实体就不会被跟踪。
然后,当您需要更改实体时,可以通过跟踪将其附加到新的上下文。
答案 1 :(得分:0)
我找到了两个解决方案:
第一个是使用现有的DbContext而不进行强制转换:
这需要额外的基础设施来根据实体状态使用gereral DbContext实例或特定的PHSRP_DashboardDataModel
我不喜欢它,因为它没有解释或纠正潜在的问题,但它确实有效。
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的问题让我找到了对数据库上下文的其他引用,最终找到了第二个更好的解决方案。
此代码“撤消”的任务是时钟输出。这就是我发现我之前仍然附加的ActiveWorkSession引用的地方。
我对SessionClockOut方法中对数据库上下文的引用未包含在“using”块中。作为测试,我添加了一个分离指令 - 有效。然后我将_DBC放在“使用”块中。这允许我的原始btn_UndoButton_Click代码工作,而无需从Context中分离ActiveWorkSession对象:
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;
}
成为Entity Framework的新手我还不了解与使用DbContext相关的所有问题。