1

I'm trying to do a generic method to update Entity Framework collections, one to many. I did this method, but i'm having a problem, when i try to verify, if the element in the new collection already exists in the old one. If exists, i have to update it, rather then remove and add it again.

The code is this:

public TEntity UpdateCollection<TEntity, TChildren>(myappContext dbContext, TEntity parentObject, Expression<Func<TEntity, 
            ICollection<TChildren>>> propertyExpression, ICollection<TChildren> objectChilren) where TEntity : class where TChildren : class
        {

            var parentEntityObject = dbContext.Entry<TEntity>(parentObject);
            List<TChildren> originalChildrenData = parentEntityObject.Collection(propertyExpression).CurrentValue.ToList();

            // Updating or removing existing items
            foreach (var originalItem in originalChildrenData)
            {
                // Where the problem is: If entry was just modified, i have to update.
                var newItem = objectChilren.FirstOrDefault(x => x == originalItem);
                if (newItem != null)
                {
                    dbContext.Entry<TChildren>(originalItem).CurrentValues.SetValues(newItem);
                    dbContext.Entry<TChildren>(originalItem).State = System.Data.EntityState.Modified;
                }
                else
                {
                    dbContext.Entry<TChildren>(originalItem).State = System.Data.EntityState.Deleted;
                }
            }

            // Adding new items
            foreach(var newItem in objectChilren.Except(originalChildrenData)){
                parentEntityObject.Collection(propertyExpression).CurrentValue.Add(newItem);
            }

            parentEntityObject.State = System.Data.EntityState.Modified;
            return parentEntityObject.Entity;
        }

Instead of try to check with:

var newItem = objectChilren.FirstOrDefault(x => x == originalItem);
                    if (newItem != null)

I also tryed with:

var newItem = this.Set<TChildren>().Local.FirstOrDefault(x => x == originalItem);

But also doesn't work, always returns null. I have to get the corresponding entry and only update it.

If it's not possible, there is another generic way to update collections "one to many"?

Rodrigo Leite
  • 874
  • 8
  • 15
  • Try to compare the primary key property of the object rather than the object itself which I assume does not override Equals and GetHashKey. – Magnus Jun 06 '15 at 18:20
  • I thought it too, but how to know which are de keys? and compare them? It needs to be generic, so i can't define a primary key. I have to find which one is the key and after compare – Rodrigo Leite Jun 06 '15 at 18:23
  • 1
    See this [Entity Framework 4 How to find the primary key](http://stackoverflow.com/questions/2958921/entity-framework-4-how-to-find-the-primary-key) – Magnus Jun 06 '15 at 18:29
  • thank you, I did some think like it: http://stackoverflow.com/questions/7253943/entity-framework-code-first-find-primary-key – Rodrigo Leite Jun 07 '15 at 18:15

1 Answers1

2

I did it, comparing the key property from my collection, as Magnus suggested, like this:

public TEntity UpdateCollection<TEntity, TChildren>(myappContext dbContext, TEntity parentObject, Expression<Func<TEntity, 
            ICollection<TChildren>>> propertyExpression, ICollection<TChildren> objectChilren) where TEntity : class where TChildren : class
        {

            var parentEntityObject = dbContext.Entry<TEntity>(parentObject);
            List<TChildren> originalChildrenData = parentEntityObject.Collection(propertyExpression).CurrentValue.ToList();

            // Get key name
            var entityKeyName = GetKeyName(dbContext, originalChildrenData.Union(objectChilren).First());

            // Updating or removing existing items
            foreach (var originalItem in originalChildrenData)
            {

                var originalValueKey = originalItem.GetType().GetProperty(entityKeyName).GetValue(originalItem, null);
                var itemCompareExpression = GetCompareExpression<TChildren>(entityKeyName, originalValueKey);

                // If entry was just modified, i have to update.
                var newItem = objectChilren.FirstOrDefault(itemCompareExpression.Compile());
                if (newItem != null)
                {
                    dbContext.Entry<TChildren>(originalItem).CurrentValues.SetValues(newItem);
                    dbContext.Entry<TChildren>(originalItem).State = System.Data.EntityState.Modified;
                    // Remove item, because only 'new items' will be added after this loop
                    objectChilren.Remove(newItem);
                }
                else
                {
                    dbContext.Entry<TChildren>(originalItem).State = System.Data.EntityState.Deleted;
                }
            }

            // Adding new items
            foreach(var newItem in objectChilren)
            {
                parentEntityObject.Collection(propertyExpression).CurrentValue.Add(newItem);
            }

            parentEntityObject.State = System.Data.EntityState.Modified;
            return parentEntityObject.Entity;
        }

Methods called:

public string GetKeyName<TEntity>(myappContext dbContext, TEntity entity) where TEntity : class
        {
            ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
            ObjectSet<TEntity> set = objectContext.CreateObjectSet<TEntity>();
            return set.EntitySet.ElementType.KeyMembers.FirstOrDefault().Name;
        }

        public Expression<Func<TEntity, bool>> GetCompareExpression<TEntity>(string keyName, object value) where TEntity : class
        {
            var parameter = Expression.Parameter(typeof(TEntity), "x");
            var property = Expression.Property(parameter, keyName);
            var method = property.Type.GetMethod("Equals", new[] { property.Type });
            var convertedValue = Convert.ChangeType(value, property.Type);
            var expression = Expression.Call(property, method, Expression.Constant(convertedValue));

            return Expression.Lambda<Func<TEntity, bool>>(expression, parameter);
        }

As all my entities only have one key, i just used "First" on "GetKeyName", but it returns all keys, if necessary.

Rodrigo Leite
  • 874
  • 8
  • 15