基于深JSON数据更新EF实体

时间:2017-08-18 11:27:45

标签: c# asp.net entity-framework

我的数据结构如下所示:foo 1:* bar 1:* baz

传递给客户端时看起来像这样:

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

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

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

1 个答案:

答案 0 :(得分:5)

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

更改跟踪器在连接方案中运行良好,但使用EF的开发人员完全不使用断开连接的实体。提供的用于操纵实体状态的上下文方法可以处理具有原始数据的简化场景,但是不能很好地处理相关数据。

处理它的一般方法是从数据库加载现有数据(icluding related),然后自己检测并应用添加/更新/删除。但是考虑到所有相关数据(导航属性)类型(一对多(拥有),多对一(关联),多对多等),以及使用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();
}

有关问题以及程序包如何解决其不同方面的详细信息,请参阅Introducing GraphDiff for Entity Framework Code First - Allowing automated updates of a graph of detached entities

缺点是作者不再支持该软件包,并且如果您决定从EF6移植,也不支持EF Core(在EF Core中使用断开连接的实体有一些改进,但仍然没有&# 39;提供开箱即用的解决方案)。

但即使对于特定型号手动正确实施更新也是一个真正的痛苦。仅作比较,对于仅具有原始和集合类型导航属性的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;