문제

I have a entity with nested collections as follows

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; }
}

I'm trying to develop a routine (that when given a list of Profiles) Inserts/Updates/Deletes the entire nested object Graph

I.e Basically i want to do delete update and insert, Profiles, Plugins and Counters where applicable in the simplest and most efficient way i can

Given a new disconnected List of Profiles (NewProfiles) and a 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();
}

Helper function

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;
}

The above routines will only update the top level objects

How would i go about applying the logic i have here, to the rest of the object graph? i.e insert update and delete plugins as well as their associated counters?

Note : the majority of this code was an example found somewhere on the internet I've adapted

도움이 되었습니까?

해결책 2

I decided to solve the problem with GraphDiff, Complete solution below. I'm open to someone posting a more elegant solution.

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;
    }

}

The below is an extension method that compare values from two lists and return entities from one which are not present in the other.

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;

}

다른 팁

Some more from the internet: Reattaching Entity Graphs with the Entity Framework or DbContext Merge for detached Entities (German).

Basic idea behind both links is to use expressions to define the paths you want to include into the update.

If you do not want to define these paths then you i would suggest to use a common baseclass or interface for your pocos. Then use something like this (untested):

    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);
        }
    }

Problems here: Performance of deeply nested graphs, possibly touching items you don't want to touch and circular reference.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top