更新EF中具有1:1关系的实体

本文关键字:关系 实体 EF 更新 | 更新日期: 2023-09-27 18:25:22

我有一个有4层的应用程序:

-Core           (Models)
-Repository     (EF DbContext actions)
-Service        (Business logic)
-Web            (MVC)

我正在尝试使用以下方法更新与EF具有1:1关系的对象:

    public async Task<bool> UpdateProductTicketing(ProductTicketing ticketing)
    {
        var product = await GetProductByIdAsync(ticketing.ProductId);
        // Validation removed for simplicity
        // 'ticketing' passed validation so let's 
        // just replace it with the existing record.
        product.Ticketing = ticketing;

        _repo.ProductRepository.Update(product);
        return await _repo.SaveAsync();
    }

这适用于初始插入,但当我更新记录时,它不会像我预期的那样工作:

A first chance exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred...

实际错误消息为:

Violation of PRIMARY KEY constraint 'PK_dbo.ProductTicketing'. Cannot insert duplicate key in object 'dbo.ProductTicketing'. The statement has been terminated.

显然,PK和FK"ProductId"没有改变——那么为什么EF试图删除并插入我的记录,而不是更新它,为什么它失败了呢?

但更重要的是,我该如何防止这种情况发生。我知道我可以手动映射对象值,然后更新它——这很有效,但将两个相同的对象映射在一起很乏味,感觉不正确。

我用于检索Product对象的存储库在我的Repository层中,而上面的方法在我的Service层中。

这就是我目前解决这个问题的方式——它看起来和感觉一样脏:

    public async Task<bool> UpdateProductTicketing(ProductTicketing ticketing)
    {
        var product = await GetProductByIdAsync(ticketing.ProductId);
        // Validation removed for simplicity
        if (product.Ticketing == null)
        {
            product.Ticketing = ticketing;
        }
        else
        {
            product.Ticketing.AllowEventBooking = ticketing.AllowEventBooking;
            // Doing the same for all other properties etc
            // etc
            // etc
        }
        _repo.ProductRepository.Update(product);
        return await _repo.SaveAsync();
    }

如果不把一个对象映射到一个相同的对象,我怎么能做到这一点?

编辑

以下是上面提到的两种型号:

[Table(@"Products")]
public class Product
{
    [Key]
    public int Id { get; set; }
    public virtual ProductTicketing Ticketing { get; set; }
    // Removed others for clarity        
    [Timestamp]
    public byte[] RowVersion { get; set; }
}
[Table(@"ProductTicketing")]
public class ProductTicketing
{
    [Key, ForeignKey("Product")]
    public int ProductId { get; set; }
    public bool AllowEventBooking { get; set; }
    // Removed others for clarity
    public virtual Product Product { get; set; }
}

可能还值得注意的是,我传递给UpdateProductTicketing方法的"ProductTicketing"对象是一个根据控制器中的值创建的对象,但ID是相同的,所以我认为它应该可以工作。

更新EF中具有1:1关系的实体

我想我现在看到了问题——当您执行product.Ticketing = ticketing;时,EF将其视为一个新的插入。

为了避免这种情况,你可以做以下事情之一:

  1. 继续使用变通方法(这实际上不是一个wokaround,而是EF希望您告知何时插入和何时更新的方式)。

  2. 现在,这取决于您的其余代码和设计,但您可以获取票证并更新其属性,而不是获取产品。当然,这意味着如果找不到票务,你需要插入它,这看起来有点像你已经在使用UpdateProductTicketing了。

  3. 使用InsertOrUpdate模式(我对您的代码做了一些假设,但希望它能给您带来想法——这里的主要内容是InsertOrUpdate方法):

    public class ProductRepository : IRepository 
    {    
        private SomeContext context;
        public void InsertOrUpdate(ProductTicketing ticketing) 
        { 
            context.Entry(ticketing).State = ticketing.ProductId == 0 ? 
                                           EntityState.Added : 
                                           EntityState.Modified; 
            context.SaveChanges(); 
        } 
    }
    
    // And a generic version
    public void InsertOrUpdate<T>(T entity) where T : class
    {
        if (context.Entry(entity).State == EntityState.Detached)
            context.Set<T>().Add(entity);
        context.SaveChanges(); 
    }
    

您收到该错误是因为ef认为ProductTicket是一个新实体,并试图将该实体插入数据库。我不知道_repo.ProductRepository.Update(product)调用,但不如将ProductTicket附加到上下文并将实体状态设置为已修改的