基于深度 JSON 数据更新 EF 实体



我有一个看起来像这样的数据结构:foo 1:* bar 1:* baz

当传递给客户端时,它可能看起来像这样:

{
id: 1,
bar: [{
id: 1,
baz: []
},
{
id: 2,
baz: [{
id: 1
}]
}
]
}

在我的UI中,这由树结构表示,用户可以在其中更新/添加/删除所有级别的项目。

我的问题是,当用户进行了修改并且我将更改的数据发送回服务器时,我应该如何执行 EF 数据库更新?我最初的想法是在客户端实现脏跟踪,并利用服务器上的脏标志来知道要更新的内容。或者,也许 EF 可以足够智能地自行执行增量更新?

不幸的是,EF 对这种情况几乎没有帮助。

更改跟踪器在连接的方案中运行良好,但使用 EF 的开发人员完全忽略了使用断开连接的实体。提供的用于操作实体状态的上下文方法可以处理包含基元数据的简化方案,但不能很好地处理相关数据。

处理它的一般方法是从数据库中加载现有数据(包括相关数据(,然后自己检测并应用添加/更新/删除。但是,考虑所有相关数据(导航属性(类型(一对多(拥有(、多对一(关联(、多对多等(以及使用 EF6 元数据的困难方法使得通用解决方案非常困难。

一般 AFAIK 解决此问题的唯一尝试是 GraphDiff 包。在方案中应用对该包的修改非常简单:

using RefactorThis.GraphDiff;
IEnumerable<Foo> foos = ...;
using (var db = new YourDbContext())
{
foreach (var foo in foos)
{
db.UpdateGraph(foo, fooMap =>
fooMap.OwnedCollection(f => f.Bars, barMap =>
barMap.OwnedCollection(b => b.Bazs)));
}
db.SaveChanges();
}

有关问题以及包如何解决该问题的不同方面的详细信息,请参阅首先引入实体框架代码的 GraphDiff - 允许自动更新分离实体的图形。

缺点是作者不再支持该包,并且如果您决定从 EF6 移植,则不支持 EF Core(在 EF Core 中使用断开连接的实体有一些改进,但仍然不提供常规的现成解决方案(。

但是,即使对于特定型号,也可以手动正确实施更新,这确实是一个痛苦的过程。只是为了进行比较,对于只有基元和集合类型导航属性的 3 个简单实体,上述UpdateGraph方法的最精简等效项如下所示:

db.Configuration.AutoDetectChangesEnabled = false;
var fooIds = foos.Where(f => f.Id != 0).Select(f => f.Id).ToList();
var oldFoos = db.Foos
.Where(f => fooIds.Contains(f.Id))
.Include(f => f.Bars.Select(b => b.Bazs))
.ToDictionary(f => f.Id);
foreach (var foo in foos)
{
Foo dbFoo;
if (!oldFoos.TryGetValue(foo.Id, out dbFoo))
{
dbFoo = db.Foos.Create();
dbFoo.Bars = new List<Bar>();
db.Foos.Add(dbFoo);
}
db.Entry(dbFoo).CurrentValues.SetValues(foo);
var oldBars = dbFoo.Bars.ToDictionary(b => b.Id);
foreach (var bar in foo.Bars)
{
Bar dbBar;
if (!oldBars.TryGetValue(bar.Id, out dbBar))
{
dbBar = db.Bars.Create();
dbBar.Bazs = new List<Baz>();
db.Bars.Add(dbBar);
dbFoo.Bars.Add(dbBar);
}
else
{
oldBars.Remove(bar.Id);
}
db.Entry(dbBar).CurrentValues.SetValues(bar);
var oldBazs = dbBar.Bazs.ToDictionary(b => b.Id);
foreach (var baz in bar.Bazs)
{
Baz dbBaz;
if (!oldBazs.TryGetValue(baz.Id, out dbBaz))
{
dbBaz = db.Bazs.Create();
db.Bazs.Add(dbBaz);
dbBar.Bazs.Add(dbBaz);
}
else
{
oldBazs.Remove(baz.Id);
}
db.Entry(dbBaz).CurrentValues.SetValues(baz);
}
db.Bazs.RemoveRange(oldBazs.Values);
}
db.Bars.RemoveRange(oldBars.Values);
}
db.Configuration.AutoDetectChangesEnabled = true;

最新更新