Razor pages ef core - CRUD 如何在编辑时修改嵌套集合?数据库并发异常
Razor pages ef core - CRUD how to modify nested collection on Edit? DbConcurencyException
在我的项目中,我将 LazyLoading 与 EF Core 结合使用,我有 2 个实体:
public class OfferEntity : BaseEntity
{
public string Category { get; set; }
public virtual List<ImagePreviewEntity> ImagePreviews { get; set; }
}
public class ImagePreviewEntity: BaseEntity
{
public string PreviewUrl { get; set; }
}
我可以通过这种方式从我的创建剃刀页面创建它们(使用 js 动态添加新行):
<div class="form-group">
<input asp-for="OfferEntity.Category" class="form-control" />
</div>
<div id="ImagePreviews" class="form-group">
<input id="addImageBtn" type="button" value="Add new">
@for (var i = 0; i < Model.OfferEntity.ImagePreviews.Count; i++)
{
<input asp-for="OfferEntity.ImagePreviews[i].PreviewUrl" class="form-control" />
}
</div>
在标准中 Create.cshtml.cs 我只为绑定模型添加了第一个实体创建:OfferEntity = new() { ImagePreviews = new() { new() } };
在进行 EDIT 之前,它完全可以正常工作。我的编辑剃刀页面与创建相同。
我的 Edit.cshtml.cs:
public async Task<IActionResult> OnGetAsync(string id)
{
OfferEntity = await _context.Offers.Include(x => x.ImagePreviews).FirstOrDefaultAsync(m => m.Id == id);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
return Page();
//await _context.Offers.Include(p => p.ImagePreviews).LoadAsync();
var toRemove = OfferEntity.ImagePreviews
.Where(x => string.IsNullOrEmpty(x.PreviewUrl))
.ToList();
_context.Attach(OfferEntity).State = EntityState.Modified;
_context.ImagePreviews.RemoveRange(toRemove);
await _context.SaveChangesAsync();
return return RedirectToPage("./Index");
}
有2个问题我想不通:
- ImagePreviews 更改未保存在父实体上。
- 删除空的 ImagePreviews 会导致 DbUpdateConcurrencyException:数据库操作预计会影响 1 行但实际上会影响 0 行。
我尝试过的:
- 从 scopeFactory 注入 dbСontext
- 将删除的实体标记为状态 = 已删除
- 使用 include(imagePreview) 直接从上下文加载 dbSet 而不是附加。
- 正在从 OfferEntity.ImagePreviews 集合中删除实体
- 手鼓跳舞
我错过了什么?
在与 EF 斗争了一天之后,找到了一个不优雅但可行的解决方案。
其中一些问题是:
- 我的新实体没有 ID
- 我在“编辑剃刀”页面中的现有实体没有附加 ID。
- 我曾多次尝试附加嵌套属性。
这是我的 OnPostAsync:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
return Page();
var newImages = OfferEntity.ImagePreviews
.Where(x => string.IsNullOrEmpty(x.Id))
.ToList();
foreach (var imagePreviewEntity in newImages)
{
imagePreviewEntity.Id = Guid.NewGuid().ToString();
await _context.ImagePreviews.AddAsync(imagePreviewEntity);
}
_context.Attach(OfferEntity).State = EntityState.Modified;
var relatedPreviewIds = OfferEntity.ImagePreviews.Select(x => x.Id).ToList();
var previews = await _context.ImagePreviews
.Where(x => relatedPreviewIds.Contains(x.Id))
.ToListAsync();
previews.ForEach(entity =>
{
_context.Entry(entity).CurrentValues.SetValues(OfferEntity.ImagePreviews.First(p => p.Id == entity.Id));
if (string.IsNullOrEmpty(entity.PreviewUrl))
{
_context.Attach(entity).State = EntityState.Deleted;
return;
}
if (_context.Entry(entity).State != EntityState.Added)
_context.Attach(entity).State = EntityState.Modified;
});
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
if (!OfferEntityExists(OfferEntity.Id))
return NotFound();
throw;
}
return RedirectToPage("./Index");
}
我觉得必须有更清洁的解决方案。为嵌套列表中的 add/remove 个实体编写尽可能多的代码是很疯狂的。
在我的项目中,我将 LazyLoading 与 EF Core 结合使用,我有 2 个实体:
public class OfferEntity : BaseEntity
{
public string Category { get; set; }
public virtual List<ImagePreviewEntity> ImagePreviews { get; set; }
}
public class ImagePreviewEntity: BaseEntity
{
public string PreviewUrl { get; set; }
}
我可以通过这种方式从我的创建剃刀页面创建它们(使用 js 动态添加新行):
<div class="form-group">
<input asp-for="OfferEntity.Category" class="form-control" />
</div>
<div id="ImagePreviews" class="form-group">
<input id="addImageBtn" type="button" value="Add new">
@for (var i = 0; i < Model.OfferEntity.ImagePreviews.Count; i++)
{
<input asp-for="OfferEntity.ImagePreviews[i].PreviewUrl" class="form-control" />
}
</div>
在标准中 Create.cshtml.cs 我只为绑定模型添加了第一个实体创建:OfferEntity = new() { ImagePreviews = new() { new() } };
在进行 EDIT 之前,它完全可以正常工作。我的编辑剃刀页面与创建相同。 我的 Edit.cshtml.cs:
public async Task<IActionResult> OnGetAsync(string id)
{
OfferEntity = await _context.Offers.Include(x => x.ImagePreviews).FirstOrDefaultAsync(m => m.Id == id);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
return Page();
//await _context.Offers.Include(p => p.ImagePreviews).LoadAsync();
var toRemove = OfferEntity.ImagePreviews
.Where(x => string.IsNullOrEmpty(x.PreviewUrl))
.ToList();
_context.Attach(OfferEntity).State = EntityState.Modified;
_context.ImagePreviews.RemoveRange(toRemove);
await _context.SaveChangesAsync();
return return RedirectToPage("./Index");
}
有2个问题我想不通:
- ImagePreviews 更改未保存在父实体上。
- 删除空的 ImagePreviews 会导致 DbUpdateConcurrencyException:数据库操作预计会影响 1 行但实际上会影响 0 行。
我尝试过的:
- 从 scopeFactory 注入 dbСontext
- 将删除的实体标记为状态 = 已删除
- 使用 include(imagePreview) 直接从上下文加载 dbSet 而不是附加。
- 正在从 OfferEntity.ImagePreviews 集合中删除实体
- 手鼓跳舞
我错过了什么?
在与 EF 斗争了一天之后,找到了一个不优雅但可行的解决方案。 其中一些问题是:
- 我的新实体没有 ID
- 我在“编辑剃刀”页面中的现有实体没有附加 ID。
- 我曾多次尝试附加嵌套属性。
这是我的 OnPostAsync:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
return Page();
var newImages = OfferEntity.ImagePreviews
.Where(x => string.IsNullOrEmpty(x.Id))
.ToList();
foreach (var imagePreviewEntity in newImages)
{
imagePreviewEntity.Id = Guid.NewGuid().ToString();
await _context.ImagePreviews.AddAsync(imagePreviewEntity);
}
_context.Attach(OfferEntity).State = EntityState.Modified;
var relatedPreviewIds = OfferEntity.ImagePreviews.Select(x => x.Id).ToList();
var previews = await _context.ImagePreviews
.Where(x => relatedPreviewIds.Contains(x.Id))
.ToListAsync();
previews.ForEach(entity =>
{
_context.Entry(entity).CurrentValues.SetValues(OfferEntity.ImagePreviews.First(p => p.Id == entity.Id));
if (string.IsNullOrEmpty(entity.PreviewUrl))
{
_context.Attach(entity).State = EntityState.Deleted;
return;
}
if (_context.Entry(entity).State != EntityState.Added)
_context.Attach(entity).State = EntityState.Modified;
});
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
if (!OfferEntityExists(OfferEntity.Id))
return NotFound();
throw;
}
return RedirectToPage("./Index");
}
我觉得必须有更清洁的解决方案。为嵌套列表中的 add/remove 个实体编写尽可能多的代码是很疯狂的。