Question

I am using ASP.NET core with EF core. My situation is following:

enter image description here

I have administration, where I can edit specific group. When I want to add user to group I have to do it by <select>, where I load all users. For example:

<select id="Users" multiple="multiple" name="Users">
    <option selected="selected" value="1">example.user</option>
    <option value="5">sharp.john</option>
    <option value="6">bruce.lee</option>
</select>

So this works fine. When I submit form, I get List<int> with user ids.

Than I create complete group (because I am also getting other data in form):

// Create list of GroupUser
var groupUsers = new List<GroupUser>();

foreach (var userId in groupsEditViewModel.Users)
{
    // Create GroupUser
    var groupUser = new GroupUser
    {
        UserId = userId
    };

    // Add it to the list of GroupUser
    groupUsers.Add(groupUser);
}

// Create group from viewmodel
var group = new Group
{
    Name = name,
    Description = groupsEditViewModel.Description,
    Priority = groupsEditViewModel.Priority,
    GroupUsers = groupUsers
};

Still everything OK, but then when I call my repository to update this value, it become something crazy, just because updating GroupUsers.

I've wanted to use this:

public void EditGroup(Group group)
{
    // Get group from db
    var groupToEdit = _authDbContext.Groups
        .SingleOrDefault(g => g.Name == group.Name);

    // Add GroupId to GroupUsers
    foreach (var groupUser in group.GroupUsers)
    {
        groupUser.GroupId = groupToEdit.GroupId;
    }

    // Update GroupUsers
    groupToEdit.GroupUsers = group.GroupUsers;

    // Save to the db
    _authDbContext.SaveChanges();
}

But it ended with error:

The instance of entity type 'GroupUser' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.

So I've ended up doing something that works. Something like this:

public void EditGroup(Group group)
{
    // Get group from db
    var groupToEdit = _authDbContext.Groups
        .SingleOrDefault(g => g.Name == group.Name);

    // Add GroupId to GroupUsers
    foreach (var groupUser in group.GroupUsers)
    {
        groupUser.GroupId = groupToEdit.GroupId;
    }

    // Copy to edit
    var groupUserToEditCopy = new List<GroupUser>(groupToEdit.GroupUsers);

    // List to remove
    foreach (var groupUserToEdit in groupToEdit.GroupUsers)
    {
        // Load UserId and GroupId
        var userId = groupUserToEdit.UserId;
        var groupId = groupUserToEdit.GroupId;

        // Check if there is still this groupUser
        if (group.GroupUsers.Count(g => g.GroupId == groupId && g.UserId == userId) == 0)
        {
            // If not, than remove this groupUser
            groupUserToEditCopy.RemoveAll(g => g.GroupId == groupId && g.UserId == userId);
        }
    }

    // List to add
    foreach (var groupUser in group.GroupUsers)
    {
        // Load UserId and GroupId
        var userId = groupUser.UserId;
        var groupId = groupUser.GroupId;

        // Check if there is new groupUser
        if (groupToEdit.GroupUsers.Count(g => g.GroupId == groupId && g.UserId == userId) == 0)
        {
            // If not, than add this groupUser
            groupUserToEditCopy.Add(groupUser);
        }
    }

    // Modified copy to original
    groupToEdit.GroupUsers = groupUserToEditCopy;

    // Save to the db
    _authDbContext.SaveChanges();
}

I think, that this solution is not optimal, and there must be way to do this easier. Do you have solution for this?

1
1
2/16/2018 11:59:41 AM

Accepted Answer

I can answer it here with your classes. this does however require that all Users and Groups are already in the Database.

Then use this extension i made to remove the unselected and add the newly selected to the list

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

using it looks like this

 var model = db.Groups
             .Include(x => x.GroupUser)
             .FirstOrDefault(x => x.GroupId == group.GroupId);

 db.TryUpdateManyToMany(model.GroupUser, listOfNewUsers
 .Select(x => new GroupUser
 {
     UserId = x,
     GroupId = group.GroupId
 }), x => x.UserId );
1
3/24/2017 8:45:22 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow