使用 EntityFramework 更新 parent 和 children

Upsert parent and children with EntityFramework

我发现自己经常想要 EntityFramework 中的 "Upsert" 功能 - 我有一个 parent + children 实体的断开连接图,我想 "upsert" parent(及其 children)。我的意思是

这个 "mostly" 的工作原理是它保留了 parent 的 ID,但不保留 children 的 ID(children 被分配了新的 ID每个 "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();
}

这是我用来得到我想要的东西的东西。就我的目的而言,效果很好:

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

也许可以从文体的角度对其进行改进/使其更通用。基本思路是

  • 查找实体之间的公共键(存在于数据库中与替换实体)
  • 对于常用键,分离旧实体,附加新实体并标记为已修改
  • 对于 no-longer 存在的键 - 删除实体
  • 对于新键 - 添加实体