使用 EntityFramework 更新 parent 和 children
Upsert parent and children with EntityFramework
我发现自己经常想要 EntityFramework 中的 "Upsert" 功能 - 我有一个 parent + children 实体的断开连接图,我想 "upsert" parent(及其 children)。我的意思是
- 当 parent 没有 分配 ID (id = 0) 我只想插入 parent 和 children(两种情况下自动分配 ID)
- 当 parent 分配了 ID 时,我想替换数据库中现有的 parent 实体(具有相同的 ID ) 与新的。此外,应删除与 parent 关联的任何 children(parent 'owns' children)。新 parent 上的 children 应该插入到它们的位置(保留分配的 ID)。
这个 "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 存在的键 - 删除实体
- 对于新键 - 添加实体
我发现自己经常想要 EntityFramework 中的 "Upsert" 功能 - 我有一个 parent + children 实体的断开连接图,我想 "upsert" parent(及其 children)。我的意思是
- 当 parent 没有 分配 ID (id = 0) 我只想插入 parent 和 children(两种情况下自动分配 ID)
- 当 parent 分配了 ID 时,我想替换数据库中现有的 parent 实体(具有相同的 ID ) 与新的。此外,应删除与 parent 关联的任何 children(parent 'owns' children)。新 parent 上的 children 应该插入到它们的位置(保留分配的 ID)。
这个 "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 存在的键 - 删除实体
- 对于新键 - 添加实体