繁体   English   中英

使用自动映射器时 EF Core 更新嵌套实体的问题

[英]Problem with EF Core updating nested entities when using automapper

我正在维护一个使用 EF Core 将数据保存到 SQL 数据库的应用程序。

我正在尝试实现一个新功能,它要求我从数据库中检索一个对象(让我们假设它是一个订单)操作它和一些附加到它的订单行并将其保存回数据库。 这不是问题,但我继承了其中的一些代码,因此需要尝试坚持现有的做事方式。

数据访问的基本过程是:

UI -> API -> 服务 -> 存储库 -> DataContext

回购中的方法遵循这种模式(尽管我为了这个问题的目的已经简化了它)

public Order GetOrder(int id)
{
    return _context.Orders.Include(o=>o.OrderLines).FirstOrDefault(x=>x.Id == id);
}

服务是应用业务逻辑和映射到 DTO 的地方,这就是 GetOrder 方法的样子:

public OrderDTO GetOrder(int id)
{
    var ord = _repo.GetOrder(id);

    return _mapper.Map<OrderDto>(ord);
}

所以为了检索和操作一个订单,我的代码看起来像这样

public void ManipulateAnOrder()
{
    // Get the order DTO from the service
    var order = _service.GetOrder(3);

    // Manipulate the order
    order.UpdatedBy = "Daneel Olivaw";
    order.OrderLines.ForEach(ol=>ol.UpdatedBy = "Daneel Olivaw");

    _service.SaveOrder(order);
}

服务中允许将其保存回数据库的方法将如下所示:

public void SaveOrder(OrderDTO order)
{
    // Get the original item from the database
    var original = _repo.GetOrder(order.Id);

    // Merge the original and the new DTO together
    _mapper.Map(order, original);

    _repo.Save(original);
}

最后,存储库保存方法如下所示

public void Save(Order order){

    _context.Update(order)
    _context.SaveChanges();

}

我遇到的问题是使用这种将实体从上下文映射到 DTO 并返回的方法会导致嵌套对象(在本例中为 OrderLines)被 AutoMapper 以这样的方式更改(或重新创建)EF 不再承认他们是它刚刚给我们的实体。

这会导致在更新时出错

InvalidOperationException 无法跟踪 ProductLine 的实例,因为已跟踪另一个具有相同 {'Id'} 键值的实例。

现在对我来说,并不是有另一个被跟踪对象的实例,它是同一个,但我知道映射过程已经破坏了该链接,EF 无法再确定它们是同一个对象。

所以,我一直在寻找解决这个问题的方法,有两种方法让我觉得很有希望,

  1. 这里提到的答案EF & Automapper。 更新嵌套集合

  2. Automapper.Collection

Automapper.collection 似乎是更好的路线,但我找不到一个很好的使用它的工作示例,而且我所做的实现似乎不起作用。

因此,我正在寻求任何之前成功使用过 automapper 集合的人的建议,或者任何对如何最好地解决这个问题有任何建议的人。

编辑,我以一个快速控制台应用程序为例,请注意,当我说快速时,我的意思是......可怕的是没有 DI 或类似的东西,我已经取消了存储库和服务以保持简单。

我还留下了一个注释掉的映射器配置文件,它确实有效,但并不理想.. 当您查看它时,您会明白我的意思。

回购在这里https://github.com/DavidDBD/AutomapperExample

好的,在检查了每个场景并指望我做了你在我之前的项目中尝试做的事情之后,它开箱即用。

将您的EntityFramework Core nuget 包更新到最新的稳定版本 (3.1.8) 解决了该问题,而无需修改您的代码。

AutoMapper 实际上“已经断开了那个链接”,并且您尝试保存的映射实体是一组新对象,以前没有被您的DbContext跟踪。 如果映射的实体是相同的对象,则不会出现此错误。

实际上,它与 AutoMapper 和映射过程无关,而是如何使用DbContext以及如何管理实体状态。

在获取映射实体后的ManipulateAnOrder方法中 -

var order = _service.GetOrder(3);

您的DbContext实例仍然存在,并且在存储库层它正在跟踪您刚刚检索的实体,同时您正在修改映射的实体 -

order.UpdatedBy = "Daneel Olivaw";
order.OrderLines.ForEach(ol=>ol.UpdatedBy = "Daneel Olivaw");

然后,当您尝试保存修改后的实体时 -

_service.SaveOrder(order);

这个映射的实体到达存储库层, DbContext尝试将它们添加到其跟踪列表中,但发现它已经在列表中具有相同类型的实体(之前获取的实体)。 EF 只能跟踪具有特定键的特定类型的一个实例。 因此,抱怨信息。

解决这个问题的一种方法是在获取Order ,告诉 EF 不要跟踪它,就像在您的存储库层一样 -

public Order GetOrder(int id, bool tracking = true)  // optional parameter
{
    if(!tracking)
    {
        return _context.Orders.Include(o=>o.OrderLines).AsNoTracking().FirstOrDefault(x=>x.Id == id);
    }
    return _context.Orders.Include(o=>o.OrderLines).FirstOrDefault(x=>x.Id == id);
}

(或者您可以添加一个单独的方法来处理NoTracking调用),然后在您的服务层 -

var order = _repo.GetOrder(id, false);  // for this operation tracking is false

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM