Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
137 views
in Technique[技术] by (71.8m points)

c# - Can I take advantage of List<T> functions Remove(T) and Insert(Int32, T) when using UpdateRange(IEnumerable<T>)?

I am attempting to implement a controller method to reorder image indexes that need to be saved in the database using EF Core.

I have the following controller method:

[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)
}

The order and data of l_oItemImages when calling UpdateRange() and subsequently SaveChangesAsync() appears correct to me.

I've been looking at this question which mentions not creating new classes and using UpdateRange(). This seems a bit different but I can see how this might be my issue.

Am I having this issue because I'm manipulating the objects of the list using Remove(l_oItemImage) and then Insert(p_iNewOrderIndex, l_oItemImage)? Or is it because I'm using ToListAsync() to begin with when I grab the item images?

EDIT: Tried Update(l_oItemImage) in place of UpdateRange(l_oItemImages) with same results. Added image of QuickWatch showing tacked entities both are correctly showing State = Modified as well as the expected changed values for int Order and bool IsPrimary properties.

QuickWatch for DBContext.ChangeTracker.Entries()

EDIT 2: Added image of QuickWatch data with highlighted changed properties on entities.

QuickWatch data for full entities modified.

question from:https://stackoverflow.com/questions/65852486/can-i-take-advantage-of-listt-functions-removet-and-insertint32-t-when-us

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Yes, you should be able to take advantage of the List methods however I think UpdateRange is unnecessary for this common task, here is an alternative implementation.

You may want to consider something like the following instead where the Sequence is reassigned for a subset of sequenced entities:

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();
}

Also, as a matter of simplification on your ItemImage object, I notice you have an apparently DB persisted property "IsPrimary" - you may want to change this to be calculated on the entity and even at the db level instead, eg:

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

For a calculated column in your MSSQL Database you can query against, add to your DbContext OnModelCreating:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...