EF4 更新导航 属性 一个实体对象不能被 IEntityChangeTracker 的多个实例引用

EF4 update navigation property An entity object cannot be referenced by multiple instances of IEntityChangeTracker

我正在尝试实现一个 "change my account email address" 功能。

我想备份 (R_EmailAddressHistory table) 中的所有用户电子邮件。

下面是我项目的一些代码。

public bool ChangeEmailAddress(string username, string newEmailAddress,  string callbackUrl)
{
    DateTime currentUtcTime = DateTime.UtcNow;

    R_User currentUser = UserRepo.GetSingle(whereCondition: w=>w.Username == username);
    currentUser.UpdateDate = currentUtcTime;

    if (currentUser.HasPendingNewEmail)
    {
        R_EmailAddressHistory currentPendingRequest = EmailHistoRepo.GetSingle(whereCondition: w => w.StatusID == (int)Reno.Common.Enums.RecordStatus.Pending && w.R_User.GId == currentUser.GId);
        currentPendingRequest.NewEmail = newEmailAddress;
        currentPendingRequest.UpdateDate = currentUtcTime;

        EmailHistoRepo.Update(currentPendingRequest);
    }
    else
    {
        currentUser.HasPendingNewEmail = true;

        R_EmailAddressHistory newEmail = new R_EmailAddressHistory();
        newEmail.UserId = currentUser.GId;
        newEmail.R_User = currentUser;
        newEmail.NewEmail = newEmailAddress;
        newEmail.InsertDate = currentUtcTime;
        newEmail.StatusID = (int) Reno.Common.Enums.RecordStatus.Pending;
        currentUser.R_EmailAddressHistory.Add(newEmail);
    }

    IdentityResult idtResult = UserRepo.Update(currentUser);

    if(idtResult == IdentityResult.Succeeded)
    {
        //Send notification to current email address for validation before proceeding change email process
        bool sendResult = Communication.EmailService.SendChangeEmailValidation(username,currentUser.Email, newEmailAddress, callbackUrl);
        return sendResult;
    }
    else
    {
        return false;
    }
}

前面的方法是用来更改邮箱地址。我的每个 table(R_User 和 EmailAddressHistory)都有存储库(UserRepo 和 EmailHistoRepo)。实现相同的 IRepositoryBase class,这里是更新方法

public IdentityResult Update(T entity)
{
    try
    {
        if (_currentContext.DbContext.Entry(entity).State == EntityState.Detached)
        {
            _currentContext.DbContext.Set<T>().Attach(entity);
        }

        _currentContext.DbContext.Entry(entity).State = EntityState.Modified;

        return IdentityResult.Succeeded;
    }
    catch
    {
        return IdentityResult.Failed;
    }
}

当用户已经有一个未验证的新电子邮件地址时,当他请求更改他当前的电子邮件地址时,我会向他显示待处理的新电子邮件地址,他可以更改它,在这种情况下,我想更新我的历史table 而不是创建一个新的,因为只允许一个待处理的新电子邮件地址。在这种情况下,我的代码在行 EmailHistoRepo.Update(currentPendingRequest) 中失败并抛出错误:实体对象不能被 IEntityChangeTracker 的多个实例引用。

谁能帮帮我? 谢谢

编辑

我正在使用带有 unitOfWork 的 MVC(4)。第一次查询数据库时,我的 UOW 在控制器中初始化,提交在 Appalication_EndRequest 中的 global.asax 文件中完成(见下文)。

        protected void Application_EndRequest(Object sender, EventArgs e)
    {
        CommitChanges();
    }

    private void CommitChanges()
    {
        Reno.BLL.Services.Singleton.UnitOfWork unitOfWork = Reno.BLL.Services.Singleton.UnitOfWork.GetCurrentInstance(false);
        if (unitOfWork != null)
        {
            unitOfWork.Commit();
            unitOfWork.Dispose();
        }

    }

您的 currentUser 在更新电子邮件地址之前被修改。首先保存对 currentUser 的更改。

像这样:

R_User currentUser = UserRepo.GetSingle(whereCondition: w=>w.Username == username);
currentUser.UpdateDate = currentUtcTime;

bool pendingNewEmail = currentUser.HasPendingNewEmail;
 UserRepo.Update(currentUser);

if (pendingNewEmail)
{
    R_EmailAddressHistory currentPendingRequest = EmailHistoRepo.GetSingle(whereCondition: w => w.StatusID == (int)Reno.Common.Enums.RecordStatus.Pending && w.R_User.GId == currentUser.GId);
    currentPendingRequest.NewEmail = newEmailAddress;
    currentPendingRequest.UpdateDate = currentUtcTime;

    EmailHistoRepo.Update(currentPendingRequest);
}
else

我终于找到了答案。问题是当我第一次让用户排队时

 R_User currentUser = UserRepo.GetSingle(whereCondition: w=>w.Username == username);

currentUser 变量持有其所有 R_EmailAddressHistory 的引用。 然后,我查询数据库(第二次)以获取待处理的电子邮件更改请求(或键入 R_EmailAddressHistory)以修改其新电子邮件及其更新日期,行

R_EmailAddressHistory currentPendingRequest = EmailHistoRepo.GetSingle(whereCondition: w => w.StatusID == (int)Reno.Common.Enums.RecordStatus.Pending && w.R_User.GId == currentUser.GId);
            currentPendingRequest.NewEmail = newEmailAddress;
            currentPendingRequest.UpdateDate = currentUtcTime;

但是最后的代码仅更新 currentPendingRequest,而 currentUser.R_EmailAddressHistory 中同一对象的另一个引用未更新并且已被上下文跟踪。因此,通过更新新实例 (EmailHistoRepo.Update(currentPendingRequest)),代码失败:如果在 2 个地方引用相同的对象。

所以,解决方案是(我唯一修改的地方):

            R_User currentUser = UserRepo.GetSingle(whereCondition: w=>w.Username == username);
        currentUser.UpdateDate = currentUtcTime;

        if (currentUser.HasPendingNewEmail)
        {
            R_EmailAddressHistory currentPendingRequest = currentUser.R_EmailAddressHistory.Where(h => h.StatusID == (int)Reno.Common.Enums.RecordStatus.Pending).First(); // EmailHistoRepo.GetSingle(whereCondition: w => w.StatusID == (int)Reno.Common.Enums.RecordStatus.Pending && w.R_User.GId == currentUser.GId);
            currentPendingRequest.NewEmail = newEmailAddress;
            currentPendingRequest.UpdateDate = currentUtcTime;

        }

我决定修改 currentUser 变量中的实例。