I need some guidance understanding what is going on "behind the hood" when an addRange() method is called on a DbSet.
I discovered a DbContext extension method which (should) easily update items in a Many-To-Many relationship. Link here: https://stackoverflow.com/a/42993990/6480913.
Here's the extension just incase the question is deleted:
public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
{
db.Set<T>().RemoveRange(currentItems.Except(newItems, getKey));
db.Set<T>().AddRange(newItems.Except(currentItems, getKey));
}
public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
{
return items
.GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
.SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
.Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
.Select(t => t.t.item);
}
From what I understand, this extension method compares two collections of items and marks the ones that were removed as Removed and marks those that were added as Added in the ChangeTracker. However, when stepping through my code, I noticed that it was actually modifying my local variable file.FileApprovers with the new added values.
Notice how there are 3 items appended to the bottom of this collection with Type: RadFX.Domain.Models.FileApprover? Those were appended to the collection after running TryUpdateManyToMany. This causes an issue because now Indexes 0/2 and 1/3 are duplicates. Index 4 is the new value that was added on form submission and I need to still use file.fileApprovers beneath this method to see the "existing" approvers. If AddRange() really does append new items to a collection, why doesn't RemoveRange() remove the first two values then?
More context on my code:
// update m-2-m table when adding or remove users
db.TryUpdateManyToMany(file.FileApprovers, approvers
.Select(x => new FileApprover
{
FileId = file.Id,
Username = x.Username
}), x => x);
// I figured that file.FileApprovers would be left unchanged so that I can
// get the usernames of the "existing approvers"
// for example, if I had 2 approvers, but added another one on form submission
// this variable should still just contain 2 approvers
// aka the items of Type: Castles.Proxies.FileApproverProxy
var existingApprovers = file.FileApprovers.Select(fa => fa.Username).ToList();
FileApprover.cs
// this is a join table class between files and approvers
public class FileApprover
{
[Key]
public string Username { get; set; }
[Key]
public int FileId { get; set; }
public virtual RtdbFile File { get; set; }
public virtual User User { get; set; }
}
The approvers variable used in the second argument of the TryUpdateManyToMany() is a List of User items that were submitted from the form:
User.cs
public class User
{
[Key]
public string Username { get; set; }
public int BadgeNumber { get; set; }
public string DisplayName { get; set; }
public virtual ICollection<FileApprover> FileApprovers { get; set; }
}
