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