基于深JSON数据更新EF实体

fil*_*lur 6 c# asp.net entity-framework

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

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

{
    id: 1,
    bar: [{
            id: 1,
            baz: []
        },
        {
            id: 2,
            baz: [{
                id: 1
            }]
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

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

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

Iva*_*oev 5

不幸的是,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();
}
Run Code Online (Sandbox Code Playgroud)

请参阅实体框架代码首先介绍GraphDiff - 允许自动更新分离实体的图形,以获取有关问题的更多信息以及程序包如何解决它的不同方面.

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

但是,即使对于特定模型,手动正确实现更新也是一个真正的痛苦.仅用于比较,UpdateGraph对于仅具有原始和集合类型导航属性的3个简单实体,上述方法的最精简等价物将如下所示:

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;
Run Code Online (Sandbox Code Playgroud)