首页 > 解决方案 > 实体框架:使用包含在 Net Core 3 中的更新记录

问题描述

如何使用 Entity Framework Core 中的包含更新表?以下似乎更新了客户交易,但不是产品 ID。

Customer 和 Product 的属性都发生了变化,但 CustomerId 和 ProductId 保持不变。

public void ModifyTransaction(IEnumerable<CustomerTransaction> customerTransactionList)
{
    foreach (var modifyItem in customerTransactionList)
    {
        var existingItem = _dbContext.Set<CustomerTransaction>().Include(x => x.Product)
                                        .FirstOrDefault(x => x.CustomerTransactionId == modifyItem.CustomerTransactionId );

        if (existingItem == null)
        {
            _dbContext.Add(existingItem );
        }
        else
        {
            _dbContext.Entry(existingItem).State = EntityState.Modified;
        }
    }
    _dbContext.SaveChanges();
}

使用网络核心 3.1

标签: c#.netentity-framework.net-coreentity-framework-core

解决方案


此代码很容易出现错误,这些错误会根据场景逐渐出现。在客户端和服务器之间传递实体类时,重要的是要了解传递回服务器的对象仅仅是序列化的副本,而不是跟踪的实体。由于序列化的工作方式,其中一个 DbContext 获取两个引用具有 ID: 14 的 Product 的事务记录将引用相同的实体实例,反序列化时,同一对事务将有两个单独的对象引用,每个引用一个 Product编号:14。

鉴于您的示例,您至少需要执行以下操作:

foreach (var modifyItem in customerTransactionList)
{
    var existingItem = _dbContext.CustomerTransactions
         .Include(x => x.Product)
         .SingleOrDefault(x => x.CustomerTransactionId == modifyItem.CustomerTransactionId );
    var trackedProduct = _dbContext.Products.Local(x => x.ProductId == modifyItem.Product.ProductId).SingleOrDefault();
    if (trackedProduct != null)
       modifyItem.Product = trackedProduct;
    else
       _dbContext.Products.Attach(modifyItem.Product);
    
    if (existingItem == null)
        _dbContext.Add(modifyItem);
    else
    {
        _dbContext.Entry(existingItem).CurrentValues.SetValues(modifyItem);
        if(existingItem.Product.ProductId != modifyItem.Product.ProductId)
            existingItem.Product = modifyItem.Product; // tracked reference.
    }
}
_dbContext.SaveChanges();

}

这相当于检查您正在做的现有交易。但是,我们还必须检查DbContext 可能正在跟踪的相关实体(产品)的任何缓存副本。如果我们不这样做并且我们尝试添加一个具有与上下文已经跟踪的 ID 匹配的产品的交易,我们将获得 PK 违规或创建新产品 ID 的重复产品记录,具体取决于您的产品PK 已配置。(即DatabaseGenerated.Identity列)如果找到一个本地缓存实例,我们将使用本地缓存实例更新产品引用,否则我们告诉 DbContext 开始跟踪产品实例。这假设这个方法不能接受新的产品作为此调用的一部分,并且收到的产品记录应合法存在。处理新产品和验证传入的产品需要额外的条件代码和数据库检查。从那里我们确定事务是更新还是插入。在更新的情况下,我们可以使用CurrentValues.SetValues跨值复制(如上)或 Automapper,或手动复制相关值。假设交易可能会更改产品,我们还会根据修改后的 ID 检查产品 ID,如果不同,我们会更新产品参考。modifyItem.Product此时将指向 DbContext 跟踪的引用。

使用这样的方法更新实体可能非常复杂,因为您不仅要考虑检测新记录还是现有记录,还要考虑更新对 DbContext 已经跟踪的实体的引用。我强烈建议对显式添加与更新操作采用视图模型,并尽可能以原子方式处理操作。即,与其传递可能包含要处理的更新或插入的事务集合,不如对每种单一类型的更改进行更精细的调用。(操作更简单、更快,出错的地方更少)


推荐阅读