更新断开的对象图

时间:2014-04-02 05:57:38

标签: c# .net entity-framework dbcontext

我有一个嵌套集合的实体,如下所示

public class Profile
{
    [Required]
    public String Id { get; set; }
    public String Name { get; set; }
    public virtual ICollection<Plugin> Plugins { get; set; }
}

public class Plugin
{
    [Required]
    public int Id { get; set; }
    public String Name { get; set; }
    public virtual ICollection<Counter> Counters{ get; set; }
}

public class Counter
{
    [Required]
    public int Id { get; set; }
    public int SomeVal { get; set; }
}

我正在尝试开发一个例程(当给出一个Profiles列表时)Inserts / Updates / Deletes整个嵌套对象Graph

I.e基本上我想以最简单,最有效的方式删除更新和插入,配置文件,插件和计数器

给定一个新的断开连接的配置文件列表(NewProfiles)和一个ClientId

using (var dc = new JCEContext())
{

    var existingProfiles = dc.Profiles.AsNoTracking().Where(x => Id == ClientId).ToList();
    var addedProfiles = NewProfiles.Except(existingProfiles, x => x.Id).ToList();
    var deletedTeachers = existingProfiles.Except(Profiles, x => x.Id);
    var modifiedProfiles = dcmProfiles.Except(addedProfiles, x => x.Id);

    addedProfiles.ToList().ForEach(tchr => dc.Entry(tchr).State = EntityState.Added);
    deletedTeachers.ToList().ForEach(tchr => dc.Entry(tchr).State = EntityState.Deleted);

    foreach (var profile in modifiedProfiles)
    {

        var entity = dc.Profiles.Find(profile.Id);

        if (entity == null)
            continue;

        var entry = dc.Entry(entity);

        entry.CurrentValues.SetValues(profile);

    }
    dc.SaveChanges();
}

辅助功能

public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other,Func<T, TKey> getKey)
{
    return from item in items
           join otherItem in other on getKey(item)
           equals getKey(otherItem) into tempItems
           from temp in tempItems.DefaultIfEmpty()
           where ReferenceEquals(null, temp) || temp.Equals(default(T))
           select item;
}

以上例程只会更新顶级对象

我如何将我在这里的逻辑应用到对象图的其余部分?即插入更新和删除插件及其相关的计数器?

注意:此代码的大部分内容都是在互联网上找到的一个例子我已经改编了

2 个答案:

答案 0 :(得分:1)

互联网上的其他内容:Reattaching Entity Graphs with the Entity FrameworkDbContext Merge for detached Entities(德语)。

两个链接背后的基本思想是使用表达式来定义要包含在更新中的路径。

如果您不想定义这些路径,那么我建议您为您的pocos使用公共基类或接口。 然后使用类似的东西(未经测试):

    public static void MergeObject<T>(this DataContext dc, T modifiedItem)
    {
        MergeObjectGraph<T>(dc, new List<T>() { modifiedItem });
    }

    public static void MergeObjectGraph<T>(this DataContext dc, IEnumerable<T> modifiedCollection)
    {
        var existingItems = dc.Set<T>.AsNoTracking().ToList();
        var addedItems = modifiedCollection.Except(existingItems, x => x.Id).ToList();
        var deletedItems = existingItems.Except(modifiedCollection, x => x.Id);

        addedItems.ToList().ForEach(tchr => dc.Entry(tchr).State = EntityState.Added);
        deletedItems.ToList().ForEach(tchr => dc.Entry(tchr).State = EntityState.Deleted);

        foreach (var item in modifiedCollection)
        {
            var navigationProperties = profile.GetType().GetProperties().Where(p => p.PropertyType.Equals(YourBaseType)).ToList();
            var nestedCollections = profile.GetType().GetProperties().Where(p => p.IsGenereicType && p.GetGenericTypeDefinition() == typeof(ICollection<>));

            foreach (var navProp in navigationProperties)
            {
                var p = navProp.GetValue(item,null);
                dc.MergeObject(navProp); //need to call this by reflection
            }

            foreach (var nested in nestedCollections)
            {
                var coll = nested.GetValue(item,null);
                dc.MergeObjectGraph(coll); //need to call this by reflection
            }
            var entity = dc.Set<T>.Find(item.Id);

            if (entity == null)
                continue;

            var entry = dc.Entry(entity);

            entry.CurrentValues.SetValues(item);
        }
    }

此处存在的问题:深度嵌套图表的性能,可能涉及您不想触摸的项目和循环引用。

答案 1 :(得分:1)

我决定用下面的GraphDiff完整解决方案解决问题。我向那些发布更优雅解决方案的人开放。

public Boolean SubmitProfiles(String dcmClientId, List<DcmProfile> dcmProfiles)
{

    try
    {

        using (var dc = new JCEContext())
        {
            var existing = dc.DcmProfiles.AsNoTracking().Where(x => x.DcmClientId == dcmClientId).ToList();
            var added = dcmProfiles.Except(existing, x => x.Id).ToList();
            var deleted = existing.Except(dcmProfiles, x => x.Id).ToList();
            var modified = dcmProfiles.Except(added, x => x.Id);

            // Update modified profiles
            foreach (var dcmProfile in modified)
                dc.UpdateGraph(dcmProfile, map => map
                    .OwnedCollection(profile => profile.Plugins, with => with
                        .OwnedCollection(plugin => plugin.Counters)));

            // Add new profiles
            added.ForEach(profile => dc.Entry(profile).State = EntityState.Added);

            // Delete nonexistent profiles
            deleted.ForEach(profile => dc.Entry(profile).State = EntityState.Deleted);

            dc.SaveChanges();

        }

        return true;

    }
    catch (Exception ex)
    {
        Log.ErrorException(ex.Message, ex);
        return false;
    }

}

以下是一种扩展方法,用于比较两个列表中的值和从另一个列表中返回实体的值。

public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKey)
{
    return from item in items
            join otherItem in other on getKey(item)
            equals getKey(otherItem) into tempItems
            from temp in tempItems.DefaultIfEmpty()
            where ReferenceEquals(null, temp) || temp.Equals(default(T))
            select item;

}