Я обнаружил, что мне часто нужна функция «Upsert» в EntityFramework — у меня есть несвязанный график родительских и дочерних объектов, и я хочу «upsert» родителя (и его дочерние элементы). Под этим я подразумеваю
Это «в основном» работает в том смысле, что он сохраняет идентификатор родителя, но не сохраняет идентификаторы дочерних элементов (дочерним элементам назначаются новые идентификаторы при каждом «upsert»).
public void Upsert(ParentEntity parentEntity) {
ParentEntity existing = DB.Parents.Find(parentEntity.Id);
if (existing == null) {
DB.Parents.Add(parentEntity);
} else {
var existingChildren = DB.Children.Where(m => m.ParentId == parentEntity.Id).ToArray();
foreach (var child in parentEntity.Children) {
child.ParentId = parentEntity.Id;
}
DB.Children.RemoveRange(existingChildren);
DB.Children.AddRange(parentEntity.Children);
DB.Entry(existing).State = EntityState.Detached;
DB.Parents.Attach(parentEntity);
DB.Entry(parentEntity).State = EntityState.Modified;
}
SaveChanges();
}
PPS: я добавил суть на Github, которую обновлю ответом: gist.github.com/flakey-bit/eea47a27b605734104ece5531bb8fcb3
Это то, что я использую, чтобы получить то, что я хочу. Для моей цели это прекрасно работает:
foreach (var metric in metrics)
{
var added = context.Set(metric.GetType()).Add(metric);
foreach (var dbEntityEntry in context.ChangeTracker.Entries())
{
//If these entities exist, don't add them.
var metricContext = dbEntityEntry.Entity as MetricContext;
if (metricContext != null)
{
var found = context.MetricContext.FirstOrDefault(c => c.Context == metricContext.Context);
if (found != default(MetricContext))
{
dbEntityEntry.State = EntityState.Detached;
}
}
}
}
Это отсоединяет любые объекты, которые уже находятся в базе данных, то есть они не будут обрабатываться.
Это не относится к требованию сохранения идентификаторов для детей, где это возможно.
Это работает:
public void Upsert(ParentEntity parentEntity) {
ParentEntity existing = DB.Parents.Find(parentEntity.Id);
if (existing == null) {
DB.Parents.Add(parentEntity);
} else {
var newChildKeys = new HashSet<int>(parentEntity.Children.Select(m => m.Id));
var existingChildren = DB.Children.Where(m => m.ParentId == parentEntity.Id).ToArray();
var existingChildrenKeys = new HashSet<int>(existingChildren.Select(m => m.Id));
foreach (var existingChild in existingChildren) {
if (newChildKeys.Contains(existingChild.Id)) {
DB.Entry(existingChild).State = EntityState.Detached;
} else {
DB.Children.Remove(existingChild);
}
}
foreach (var child in parentEntity.Children) {
child.ParentId = parentEntity.Id;
if (existingChildrenKeys.Contains((child.Id))) {
DB.Children.Attach(child);
DB.Entry(child).State = EntityState.Modified;
} else {
DB.Children.Add(child);
}
}
DB.Entry(existing).State = EntityState.Detached;
DB.Parents.Attach(parentEntity);
DB.Entry(parentEntity).State = EntityState.Modified;
}
SaveChanges();
}
Возможно, его можно улучшить со стилистической точки зрения/сделать более общим. Основная идея
PS Я знаю о
AddOrUpdate
, но я не хочу его использовать (он предназначен для миграции)