更新数据库条目时尝试跟踪实体实例时出错

Error when trying to track instance of an entity while updating the database entries

我想修改我的数据库的一列table(通知table)。相关的通知 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: 'The instance of entity type 'Notification' cannot be tracked because another instance with the same key value for {'notificationId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'

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

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

我在 Program.cs 中添加了范围存储库:

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

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

控制器代码

[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;
    }
}

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

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

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

这是告诉存储库加载整个通知,然后检索它的文本 属性。有可能实例正在被 DbContext 跟踪,并且 DbContext 的范围是请求的生命周期。当您调用 Update 方法时,仍会跟踪由 GetNotificationById() 加载的实例,但您正在创建实体的新实例以传递给您的更新方法,然后在您的更新方法中创建实体的另一个实例,都具有相同的 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 以查看它是否在附加之前跟踪实体。例如,如果您修改传递到存储库的分离通知实体中的详细信息,并希望在 entity/properties:

上使用更新或附加并设置修改后的状态
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;
    }
}