【问题标题】:Can I take advantage of List<T> functions Remove(T) and Insert(Int32, T) when using UpdateRange(IEnumerable<T>)?使用 UpdateRange(IEnumerable<T>) 时,我可以利用 List<T> 函数 Remove(T) 和 Insert(Int32, T) 吗?
【发布时间】:2021-01-25 06:02:58
【问题描述】:

我正在尝试实现一个控制器方法来重新排序需要使用 EF Core 保存在数据库中的图像索引。

我有以下控制器方法:

[HttpPost]
public async Task<JsonResult> ReorderImage(int p_iImageID, int p_iNewOrderIndex)
{
     if (p_iImageID <= 0)
          return Json(new { Status = "Error", Message = $"Unable to retrieve item image with ID of {p_iImageID}" });

     ItemImage l_oItemImage = await _Context.ItemImages.FirstOrDefaultAsync(l_oImage => l_oImage.ID == p_iImageID);

     if (l_oItemImage.IsNull())
          return Json(new { Status = "Error", Message = $"Unable to retrieve item image with ID of {p_iImageID}" });


     List<ItemImage> l_oItemImages = await _Context.ItemImages.Where(l_oImage => l_oImage.ItemID == l_oItemImage.ItemID)
                                                              .OrderBy(l_oImage => l_oImage.Order)
                                                              .ToListAsync();

     l_oItemImages.Remove(l_oItemImage);
     l_oItemImages.Insert(p_iNewOrderIndex, l_oItemImage);

     foreach(ItemImage l_oImage in l_oItemImages)
     {
          l_oImage.Order = l_oItemImages.IndexOf(l_oImage);

          if (l_oItemImages.IndexOf(l_oImage) == 0)
               l_oImage.IsPrimary = true;
          else
               l_oImage.IsPrimary = false;

          l_oImage.Uri = _AzureBlobStorage.GetBlobUri(_ItemImageAzureBlobContainerName, l_oImage.GetFileName());
     }

     _Context.ItemImages.UpdateRange(l_oItemImages);
     await _Context.SaveChangesAsync();

     return Json(l_oItemImages)
}

在调用UpdateRange() 和随后的SaveChangesAsync()l_oItemImages 的顺序和数据对我来说似乎是正确的。

我一直在查看this question,其中提到不创建新类并使用UpdateRange()。这似乎有点不同,但我可以看出这可能是我的问题。

我遇到这个问题是因为我使用Remove(l_oItemImage) 然后Insert(p_iNewOrderIndex, l_oItemImage) 操作列表的对象吗?还是因为我在抓取商品图片时使用的是ToListAsync()

编辑:尝试用Update(l_oItemImage) 代替UpdateRange(l_oItemImages),结果相同。添加了 QuickWatch 的图像,显​​示了被标记的实体都正确显示了 State = Modified 以及 int Orderbool IsPrimary 属性的预期更改值。

编辑 2:添加了 QuickWatch 数据的图像,其中突出显示了实体上的更改属性。

【问题讨论】:

  • 你能给我们更多的背景信息吗?首先是您看到的确切错误,然后是类定义和对象关系映射(relationships ecc)。如果没有这些,我会告诉你打电话给DetectChanges(只是为了确定)和DbContext.ChangeTracker.Entries()来检查所涉及的条目。你真的需要打电话给UpdateRange吗?我的意思是,您是否已从 DbContext 配置中禁用跟踪?
  • @MarcoLuzzara 我没有收到任何错误。数据库只是简单地不更新更改的实体。尝试更改为foreach 并使用Update(l_oItemImage) 而不是使用UpdateRange(l_oItemImages),结果相同。我附上了一张显示DbContext.ChangeTracker.Entries() 的结果的图像,它显示了两个图像对象的正确修改数据(如果需要,我可以截取完整数据,但这是我所期望的)。我没有在DbContext 中明确禁用跟踪,其他功能似乎可以正确更新 ItemImages 上下文,所以我不知道发生了什么。

标签: c# entity-framework-core


【解决方案1】:

是的,您应该能够利用 List 方法,但我认为 UpdateRange 对于这个常见任务来说是不必要的,这里有一个替代实现。

您可能需要考虑类似以下的事情,而不是为序列实体的子集重新分配序列:

public async Task SetSequenceAsync(int forPageComponentId, int newSequence)
{
    var infoAboutItemWereChangingSequenceFor = await context.PageComponents
        .Where(x => x.Id == forPageComponentId)
        .Select(x => new  { 
            OriginalSequence = x.Sequence, // I need to know it's current sequence.
            x.PageId // I need to only adjust sequences for items that have the same PageId, so I need to know what the pageId is for the item we're targeting.
        }).FirstOrDefaultAsync();

    // Get just the data we want to modify, we're going to include the item we're targeting so this list is inclusive of it.
    // Including the item we're changing to make logic below a little mor consise instead of managing the list and the item we're targeting
    // seperately.
    var allItemsWithSequenceThatWillChange = await context.PageComponents
        .Where(x =>
            x.PageId == infoAboutItemWereChangingSequenceFor.PageId // Only those items sharing the same page Id.
            // Only those items we need to change the sequence for.
            && x.Sequence >= Math.Min(infoAboutItemWereChangingSequenceFor.OriginalSequence, newSequence)
            && x.Sequence <= Math.Max(infoAboutItemWereChangingSequenceFor.OriginalSequence, newSequence)
        )
        .Select(x =>
            new PageComponent() // The type of object EF knows about.
            {
                // The Primary key, so Entity Framework knows what record to change the sequence on.
                Id = x.Id,
                // The sequence value we need to change.
                Sequence = x.Sequence
            }
        ).ToListAsync();

    // Set the sequence of the item we're targeting.
    allItemsWithSequenceThatWillChange
        .Where(x => x.Id == forPageComponentId)
        .First()
        .Sequence = newSequence;

    // Now update the sequence on the other items (excluding the target item)
    foreach (var item in allItemsWithSequenceThatWillChange.Where(x => x.Id != forPageComponentId))
    {
        // Either increment or decrement the sequence depending on how the original item was moved.
        item.Sequence += infoAboutItemWereChangingSequenceFor.OriginalSequence > newSequence ? 1 : -1;
        // Add any other property changes here.
    }

    // Save changes.
    await context.SaveChangesAsync();
}

此外,为了简化您的 ItemImage 对象,我注意到您有一个明显的数据库持久属性“IsPrimary” - 您可能希望将其更改为在实体甚至数据库级别进行计算,例如:

public class ItemImage {
    // ... Other Properties ...
    public int Order { get; set; }
    public bool IsPrimary {
        get => Order == 0;
        set {}
    }
}

对于您可以查询的 MSSQL 数据库中的计算列,添加到您的 DbContext OnModelCreating:

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity(typeof(ImageObject)).Property("IsPrimary").HasComputedColumnSql("CASE WHEN [Order] = 0 THEN 1 ELSE 0 END");
}

【讨论】:

  • 我在这行 context.AttachRange(); 上收到一个错误。我收到的错误是System.InvalidOperationException: The instance of entity type 'ForkliftImage' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
  • 我也很欣赏您简化模型的建议。我同意并将其添加到我的积压任务中。目前,当我尝试您的答案时,我将属性添加到查询中,并在最后一个 foreach() 语句中设置。
  • 在顶部的检查之间删除这条线似乎消除了我之前评论过的错误,但对我来说,这段代码似乎还有其他问题。 ItemImage l_oItemImage = await _Context.ItemImages.FirstOrDefaultAsync(l_oImage =&gt; l_oImage.ID == p_iImageID);
  • 任何时候你从上下文中加载一个实体对象,它都会被默认跟踪——你不需要附加它。如果您两次加载相同的实体,它实际上应该返回已经加载的实体。为避免这种情况,要么 1.) 加载一个新的匿名对象,要么在查询中使用 .AsNoTracking(),例如 context.ForkLiftImages.AsNoTracking().Where(....)...
  • 在获取要更改的项目列表后,我可能错误地添加了“context.AttachRange()”,它们可能已经被跟踪 - 未经测试的代码,哈哈。如果是这样,我会更新答案。未经测试的代码答案的乐趣......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-05
  • 1970-01-01
  • 2021-08-23
  • 1970-01-01
  • 2023-04-01
  • 2012-02-24
相关资源
最近更新 更多