1

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.

enter image description here

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

1 Answer 1

0

I think your problem is not in how DbSet.AddRange functions, but rather, in how you're calling TryUpdateManyToMany:

// 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);

x => x being the problematic part. You're trying to do the join based on the reference equality of x rather than a given property.

Something like this should work fine:

// 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.FileId);
Sign up to request clarification or add additional context in comments.

4 Comments

Good catch! It shouldn't matter what property I use for reference equality (fileId or username) since both are of type FileApprover, correct? Just curious
@JordanLewallen that depends on what's the unique field for FileApprover
right, but in this case, both username and fileId make up a unique composite key
@JordanLewallen You may be able to use a Tuple for that, like x => Tuple.Create<int, string>(x.FileId, x.Username)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.