错误:IEntityChangeTracker的多个实例无法引用实体对象

时间:2017-12-01 01:55:01

标签: c# .net entity-framework entity-framework-6

所以,我只是在我的智慧结束。我是一名经验丰富的程序员和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(); 

没有解决问题 - 同样的错误

2 个答案:

答案 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相关的所有问题。