【问题标题】:Error when trying to track instance of an entity while updating the database entries在更新数据库条目时尝试跟踪实体实例时出错
【发布时间】:2022-01-19 19:31:30
【问题描述】:

我想修改我的数据库表(通知表)的一列。相关的通知 Id 由视图确定,并使用 Ajax 调用发送回控制器。我的控制器代码:

[HttpPost]
        public JsonResult UpdateNotification(int notificationId, int userId)
        {
            string notificationText = _notificationRepository.GetNotificationById(notificationId).isSeen;
            var myNotification = new Notification()
            {
                isSeen = string.Format("{0},{1}", notificationText, userId)
            };
            bool result = _notificationRepository.UpdateNotification(notificationId, myNotification);
            if (result == true)
            {
                DeleteNotification(notificationId);
                return Json(new { success = true});
            }
            else
            {
                return Json(new { success = false });
            }
        }

bool result = _notificationRepository.UpdateNotification(notificationId, myNotification); 行链接到存储库实现:

bool INotificationRepository.UpdateNotification(int selectedNotId, Notification notification)
        {
            try
            {
                var selectedNotification = new Notification()
                {
                    notificationId = selectedNotId,
                    isSeen = notification.isSeen
                };
                context.Entry(selectedNotification).Property(x => x.isSeen).IsModified = true;
                return true;
            }
            catch (Exception)
            {

                return false;
            }

        }

当我调试时,context.Entry(selectedNotification).Property(x => x.isSeen).IsModified = true; 行中出现以下错误并说:

System.InvalidOperationException: '实体类型的实例 无法跟踪“通知”,因为另一个实例与 {'notificationId'} 的相同键值已被跟踪。什么时候 附加现有实体,确保只有一个实体实例具有 附加了给定的键值。考虑使用 'DbContextOptionsBuilder.EnableSensitiveDataLogging' 查看 键值冲突。'

我尝试使用 context.Entry(selectedNotId).State = Microsoft.EntityFrameworkCore.EntityState.DetachednotificationId 从实体中分离出来,但代码停在这一行并且没有任何反应。我该如何解决这个问题?

更新: 我使用相同的 DbContext 从数据库表中读取 notificationId(在视图中)。

我在 Program.cs 中添加了作用域存储库:

builder.Services.AddScoped<INotificationRepository, NotificationRepository>();

【问题讨论】:

    标签: entity-framework asp.net-core entity-framework-core


    【解决方案1】:

    在实现诸如存储库之类的东西时,这是一个常见问题,您在其中传递实体引用并且 DbContext 的范围没有明确定义。

    问题是您对实体的重新定义太过分了。采用你的控制器方法:

    string notificationText = _notificationRepository.GetNotificationById(notificationId).isSeen;
    

    这是告诉存储库加载整个通知,然后检索它的 Text 属性。 DbContext 可能正在跟踪该实例,并且 DbContext 的范围是请求的生命周期。当您调用 Update 方法时,仍会跟踪由 GetNotificationById() 加载的实例,但您正在创建一个新的实体实例以传递给您的 update 方法,然后在您的 update 方法中创建另一个实体实例,都具有相同的 ID。

    这完全没有必要。我也想不通你为什么要麻烦更新通知,然后调用一个方法来删除通知??

    [HttpPost]
    public JsonResult UpdateNotification(int notificationId, int userId)
    {
        try
        {
            var notification = _notificationRepository.GetNotificationById(notificationId);
            notification.IsSeen = string.Format("{0},{1}", notificationText, userId);
            _context.SaveChanges();
            return Json(new { success = true});
        }
        catch(Exception)
        {
            return Json(new { success = false});
        }
    }
    

    由于存储库加载了一个实体,我们可以在该实体上使用更改跟踪,然后使用作用域 DbContext 来保存更改。这确实违背了在 DbContext 之上添加存储库层的目的,因为您可以只使用 DbContext 本身获取实体。通常使用工作单元模式,你会有更多类似的东西:

    using (var scope = _unitOfWork.CreateScope())
    {
        var notification = _notificationRepository.GetNotificationById(notificationId);
        notification.IsSeen = string.Format("{0},{1}", notificationText, userId);
        scope.SaveChanges();
    }
    

    存储库有一种方法可以从它们被调用的 UoW 范围内解析 DbContext。 (即使用定位器或将范围传递给每个方法调用)

    您要避免的主要事情是不断为同一行更新实体类。 (Id) 获取一个实体,利用 EF 的内置更改跟踪更新它的值,然后调用 SaveChanges()。如果您曾经遇到要更新的分离实体的情况,请首先检查 DbContext 以查看它是否在附加之前跟踪实体。例如,如果您修改传递到存储库的分离通知实体中的详细信息,并希望使用更新或附加并在实体/属性上设置修改状态:

    bool INotificationRepository.UpdateNotification(Notification notification)
    {
        try
        {
            var existingNotification = context.Notifications.Local.SingleOrdefault(x => x.notificationId == notification.notificationId);
            if (existingNotification != null) 
            {
                existingNotification.isSeen = notification.isSeen;
            }
            else
            {
                context.Attach(notification);
                context.Entry(selectedNotification).Property(x => x.isSeen).IsModified = true;
            }
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }
    

    【讨论】:

      【解决方案2】:

      为什么不使用更简单的方法?什么时候可以重用存储库中的实体?示例如下:

      控制器代码

      [HttpPost]
      public JsonResult UpdateNotification(int notificationId, int userId)
      {
          var notification = _notificationRepository.GetNotificationById(notificationId);
          notification.isSeen = string.Format("{0},{1}", notificationText, userId);
          
          bool result = _notificationRepository.UpdateNotification(notification);
          if (result == true)
          {
              DeleteNotification(notificationId);
              return Json(new { success = true});
          }
          else
          {
              return Json(new { success = false });
          }
      }
      

      存储库实现

      bool INotificationRepository.UpdateNotification(Notification notification)
      {
          try
          {
              context.Update(notification);
          }
          catch (Exception)
          {
              return false;
          }
      }
      

      【讨论】:

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