4

I have a generic Update method for Entity Framework in an abstract DatabaseOperations<T,U> class:

public virtual void Update(T updatedObject, int key)
{
    if (updatedObject == null)
    {
        return;
    }

    using (var databaseContext = new U())
    {
        databaseContext.Database.Log = Console.Write; 

        T foundEntity = databaseContext.Set<T>().Find(key);
        databaseContext.Entry(foundEntity).CurrentValues.SetValues(updatedObject);
        databaseContext.SaveChanges();
    }
}

However, this does not handle many-to-many relationships.

This many-to-many update problem can be overcome by overriding the Update method in TrussSetDatabaseOperations : DatabaseOperations<TrussSet, TrussManagementDatabaseContext> to read as follows:

public override void Update(TrussSet updatedTrussSet, int key)
{
    if (updatedTrussSet == null)
    {
        return;
    }

    using (var databaseContext = new TrussManagementDatabaseContext())
    {
        databaseContext.Database.Log = Console.Write;

        TrussSet foundTrussSet = databaseContext.TrussSets.Find(key);
        databaseContext.Entry(foundTrussSet).CurrentValues.SetValues(updatedTrussSet)            

        // Update the many-to-many relationship of TrussSets to Seals
        databaseContext.Entry(foundTrussSet).Collection(trussSet => trussSet.Seals).Load();
        databaseContext.Entry(foundTrussSet).Collection(trussSet => trussSet.Seals).CurrentValue = updatedTrussSet.Seals;

        databaseContext.SaveChanges();
    }
}

However, this overriding would proliferate through all the classes that inherit from DatabaseOperations and have a TrussSet object. Can I somehow inject the added two lines into the generic update method, so that the update method is given the collection properties, loads them, and applies the respective updated collection to that entity? Thanks in advance.

Scotty H
  • 6,432
  • 6
  • 41
  • 94

1 Answers1

2

Looking at your code, the following comes to mind:

public virtual void Update(T updatedObject, int key, params string[] navigationProperties) {
    if (updatedObject == null) {
        return;
    }

    using (var databaseContext = new U()) {
        databaseContext.Database.Log = Console.Write;

        T foundEntity = databaseContext.Set<T>().Find(key);
        var entry = databaseContext.Entry(foundEntity);
        entry.CurrentValues.SetValues(updatedObject);                
        foreach (var prop in navigationProperties) {
            var collection  = entry.Collection(prop);
            collection.Load();
            collection.CurrentValue = typeof(T).GetProperty(prop).GetValue(updatedObject);
        }
        databaseContext.SaveChanges();
    }
}

You can also use Expressions instead of strings (and then extract property names from those expressions) if you want more type-safety.

Update: here is what I mean by "use Expressions" in this case:

public virtual void Update(T updatedObject, int key, params Expression<Func<T, IEnumerable>>[] navigationProperties) {
    if (updatedObject == null) {
        return;
    }


    using (var databaseContext = new U()) {
        databaseContext.Database.Log = Console.Write;

        T foundEntity = databaseContext.Set<T>().Find(key);
        var entry = databaseContext.Entry(foundEntity);
        entry.CurrentValues.SetValues(updatedObject);
        foreach (var prop in navigationProperties) {
            string memberName;
            var member = prop.Body as MemberExpression;
            if (member != null)
                memberName = member.Member.Name;
            else throw new Exception("One of the navigationProperties is not a member access expression");
            var collection = entry.Collection(memberName);
            collection.Load();
            collection.CurrentValue = typeof (T).GetProperty(memberName).GetValue(updatedObject);
        }
        databaseContext.SaveChanges();
    }
}
Evk
  • 98,527
  • 8
  • 141
  • 191
  • Thanks, could you give an example with Expressions? Specifically, I'm finding that I need to have a parameter for ICollection in my Update method signature: `public virtual void Update(T updatedObject, int key, List>>> navigationCollections = null)` but that signature would vary based on who is calling the generic method, and if there are multiple collections that are being updated. – Scotty H Sep 28 '15 at 20:49
  • I may have solved that by using `object`. But with expressions, how would I update `typeof(T).GetProperty(prop).GetValue(updatedObject);`? – Scotty H Sep 28 '15 at 20:53
  • I mean a bit different thing - use expression to pass navigation property name in more type safe manner. Look here http://stackoverflow.com/q/273941/5311735 for example of how to get property name from member access expression. – Evk Sep 28 '15 at 21:07
  • @ScottH updated my answer with sample code which uses expressions. – Evk Sep 28 '15 at 21:19
  • Can this handle nested collection navigation properties? I'm having trouble with that. So it works for TrussSet.Seals, but not for TrussInstance, which has a navigation property TrussSet which then has a navigation collection of Seals. It throws an `ArgumentException`: " The property 'Seals' on type 'TrussInstance' is not a navigation property. The Reference and Collection methods can only be used with navigation properties. Use the Property or ComplexProperty method." – Scotty H Sep 29 '15 at 15:53
  • So you have some non-navigation property, and that property in turn has navigation property you want to copy? Like TrussInstance.TrussSet (< this is regular property).Seals (< this is navigation one) ? – Evk Sep 29 '15 at 16:06
  • No, TrussSet is a navigation property of TrussInstance, not a regular property. – Scotty H Sep 29 '15 at 16:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/90916/discussion-between-evk-and-scott-h). – Evk Sep 29 '15 at 16:12