我正在使用带有EF核心的ASP.NET核心。我的情况如下:
我有管理,我可以编辑特定组。当我想将用户添加到组时,我必须通过<select>
来加载所有用户。例如:
<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>
所以这很好用。当我提交表单时,我会收到List<int>
用户ID。
比我创建完整的组(因为我还在表单中获取其他数据):
// 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
};
仍然一切正常,但是当我调用我的存储库来更新这个值时,它变得很疯狂,只是因为更新了GroupUsers。
我想用这个:
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();
}
但它以错误结束:
实体类型的实例&#39; GroupUser&#39;无法跟踪,因为已经跟踪了具有相同密钥的此类型的另一个实例。添加新实体时,对于大多数密钥类型,如果未设置密钥,则将创建唯一的临时密钥值(即,如果为密钥属性指定了其类型的默认值)。如果要为新实体显式设置键值,请确保它们不会与现有实体或为其他新实体生成的临时值发生冲突。附加现有实体时,请确保只有一个具有给定键值的实体实例附加到上下文。
所以我最终做了一些有效的事情。像这样:
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();
}
我认为,这个解决方案不是最优的,必须有办法更轻松地做到这一点。你有解决方案吗?
答案 0 :(得分:1)
我可以在课堂上回答这个问题。但是,这确实要求所有用户和组都已在数据库中。
然后使用此扩展程序删除未选中的内容并将新选择的内容添加到列表中
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);
}
使用它看起来像这样
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 );