更新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是相同的,所以我认为它应该可以工作。
我想我现在看到了问题——当您执行product.Ticketing = ticketing;
时,EF将其视为一个新的插入。
为了避免这种情况,你可以做以下事情之一:
-
继续使用变通方法(这实际上不是一个wokaround,而是EF希望您告知何时插入和何时更新的方式)。
-
现在,这取决于您的其余代码和设计,但您可以获取票证并更新其属性,而不是获取产品。当然,这意味着如果找不到票务,你需要插入它,这看起来有点像你已经在使用
UpdateProductTicketing
了。 -
使用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附加到上下文并将实体状态设置为已修改的