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个问题我想不通:

  1. ImagePreviews 更改未保存在父实体上。
  2. 删除空的 ImagePreviews 会导致 DbUpdateConcurrencyException:数据库操作预计会影响 1 行但实际上会影响 0 行。

我尝试过的:

  1. 从 scopeFactory 注入 dbСontext
  2. 将删除的实体标记为状态 = 已删除
  3. 使用 include(imagePreview) 直接从上下文加载 dbSet 而不是附加。
  4. 正在从 OfferEntity.ImagePreviews 集合中删除实体
  5. 手鼓跳舞

我错过了什么?

在与 EF 斗争了一天之后,找到了一个不优雅但可行的解决方案。 其中一些问题是:

  1. 我的新实体没有 ID
  2. 我在“编辑剃刀”页面中的现有实体没有附加 ID。
  3. 我曾多次尝试附加嵌套属性。

这是我的 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 个实体编写尽可能多的代码是很疯狂的。