Ich verwende ASP.NET Core mit EF Core. Meine Situation ist folgende:
Ich habe Administration, wo ich bestimmte Gruppen bearbeiten kann. Wenn ich einen Benutzer zur Gruppe hinzufügen möchte, muss ich dies mit <select>
, wo ich alle Benutzer lade. Beispielsweise:
<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>
Das funktioniert also gut. Wenn ich das Formular absende, erhalte ich List<int>
mit Benutzer-IDs.
Dann erstelle ich eine komplette Gruppe (weil ich auch andere Daten in Form bekomme):
// 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
};
Immer noch alles in Ordnung, aber wenn ich dann mein Repository anrufe, um diesen Wert zu aktualisieren, wird es etwas verrückt, nur weil GroupUsers aktualisiert werden.
Ich wollte das verwenden:
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();
}
Aber es endete mit einem Fehler:
Die Instanz des Entitätstyps 'GroupUser' kann nicht verfolgt werden, da bereits eine andere Instanz dieses Typs mit demselben Schlüssel verfolgt wird. Beim Hinzufügen neuer Entitäten wird für die meisten Schlüsseltypen ein eindeutiger temporärer Schlüsselwert erstellt, wenn kein Schlüssel festgelegt ist (dh wenn der Schlüsseleigenschaft der Standardwert für ihren Typ zugewiesen ist). Wenn Sie explizit Schlüsselwerte für neue Entitäten festlegen, stellen Sie sicher, dass sie nicht mit vorhandenen Entitäten oder temporären Werten kollidieren, die für andere neue Entitäten generiert wurden. Achten Sie beim Anhängen vorhandener Entitäten darauf, dass nur eine Entitätsinstanz mit einem bestimmten Schlüsselwert an den Kontext angehängt ist.
Also habe ich etwas getan, was funktioniert. Etwas wie das:
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();
}
Ich denke, dass diese Lösung nicht optimal ist, und es muss einen Weg geben, dies einfacher zu machen. Hast du eine Lösung dafür?
Ich kann es hier mit deinen Klassen beantworten. Dies erfordert jedoch, dass alle Benutzer und Gruppen bereits in der Datenbank sind.
Verwenden Sie dann diese Erweiterung, die ich gemacht habe, um das nicht ausgewählte Objekt zu entfernen und das neu ausgewählte Objekt zur Liste hinzuzufügen
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);
}
es sieht so aus
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 );