【问题标题】:Error:An entity object cannot be referenced by multiple instances of IEntityChangeTracker错误:一个实体对象不能被多个 IEntityChangeTracker 实例引用
【发布时间】:2017-12-01 01:55:01
【问题描述】:

所以,我差不多到此为止了。我是一位经验丰富的程序员和 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(); 

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

【问题讨论】:

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


    【解决方案1】:

    您可以尝试在您的getActiveWorkSession() 中使用AsNoTracking()。这样,您的实体就不会被跟踪。

    然后,当您需要更改实体时,可以将其附加到带有跟踪的新上下文。

    【讨论】:

    • 感谢您的回复。我进行了更改(见上文),但没有更改错误。
    • @Ross,这很有趣。 btn_UndoButton_Click 是否有可能导致实体被跟踪两次?
    • 感谢您的建议,对您有所帮助。我确实赞成您的回答,但由于我是新手,因此显然不会显示。
    • 不用担心@Ross。那么你是如何解决你的问题的呢?
    【解决方案2】:

    我找到了两个解决方案:

    第一个是使用现有的 DbContext 而不进行强制转换:

    这需要额外的基础设施来使用一般 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 方法中对数据库上下文的引用没有包含在“使用”块中。作为测试,我添加了一个分离指令——它有效。然后我将 _DBC 放在“使用”块中。这允许我原来的 btn_UndoButton_Click 代码工作,而无需从上下文中分离 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 相关的所有问题。

    【讨论】:

      猜你喜欢
      • 2011-11-01
      • 2011-10-12
      • 1970-01-01
      相关资源
      最近更新 更多