使用1:1关系更新EF中的实体

时间:2014-10-03 02:43:31

标签: c# entity-framework

我有一个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“{​​{1}}”没有改变 - 那么为什么EF试图删除并插入我的记录而不仅仅是更新它,为什么它会失败?

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

我检索ProductId对象的存储库位于我的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; } } 方法的“ProductTicketing”对象是从我的控制器中的值创建的 new 对象 - 但是ID是相同的,所以我认为它应该工作。

2 个答案:

答案 0 :(得分:3)

我想我现在看到了问题 - 当你product.Ticketing = ticketing;时,EF将其视为新的插入。

为避免这种情况,您可以执行以下操作之一:

  1. 继续使用变通方法(实际上这不是一个错误的方法,而是EF希望您告诉您何时插入以及何时更新)。

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

  3. 使用InsertOrUpdate pattern(我对您的代码做了一些假设,但希望它能为您提供想法 - 这里的主要内容是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(); 
    }
    

答案 1 :(得分:1)

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