Shoter
Shoter

Reputation: 1039

Use Func in Linq to Entity as generic method parameter

I want to create function based on Entity Framework which will update Table in SQL database.

This table have parent-child relationship, so I am updating the bunch of children based on parentID.

I want to have a generic function and I don't know how to get the ID out of the object whose definition is not known at compilation time.

I tried to use Func which could be pretty neat, but it can't be used in Linq-To-Entities.

Code:

protected static void update<TEntity>(DbSet<TEntity> set, int parentID,
  List<TEntity> entities, Func<TEntity, bool> isNew, Func<TEntity, int> getID = null, 
  Func<TEntity, int> getParentID, Action<TEntity, TEntity> updateSingleEntity)
        where TEntity : class
    {
        var currentIDs = entities.Select(e => getID(e));

        var newEntities = entities.Where(e => isNew(e));
        var existingEntities = entities.Where(e => !isNew(e));

        var deletedEntities 
          = set.Where(e => getParentID(e) == parentID && !currentIDs.Contains(getID(e)));

        foreach (var toAdd in newEntities)
        {
            set.Add(toAdd);
        }

        foreach (var toDelete in deletedEntities)
        {
            var entity = set.Find(getID(toDelete));
            set.Remove(entity);
        }

        foreach (var toUpdate in existingEntities)
        {
            var entity = set.Find(getID(toUpdate));
            updateSingleEntity(toUpdate, entity);
        }
    }

I'd like to use this function like this:

   update(set, parentID, someList, e => e.ID != 0, e => e.ID, e => ParentID, somefunc);

It's pretty neat IMO. Is there any way to implement a function like this?

I cannot do ( I tried):

Interface which has ID property can be neat. But I am not able to define parentID then in this interface because the name of this property varies around entities in DB.

Upvotes: 0

Views: 550

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205599

I think your function should look like this:

protected static void Update<TEntity>(DbSet<TEntity> set, Expression<Func<TEntity, int>> getParentID, int parentID, List<TEntity> entities, Func<TEntity, int> getID, Action<TEntity, TEntity> updateSingleEntity)
    where TEntity : class
{
    var filter = Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(getParentID.Body, Expression.Constant(parentID)), getParentID.Parameters[0]);
    var targetEntities = set.Where(filter).ToDictionary(getID);
    foreach (var entity in entities)
    {
        var entityID = getID(entity);
        TEntity targetEntity;
        if (!targetEntities.TryGetValue(entityID, out targetEntity))
            set.Add(entity);
        else
        {
            updateSingleEntity(targetEntity, entity);
            targetEntities.Remove(entityID);
        }
    }
    if (targetEntities.Count > 0)
        set.RemoveRange(targetEntities.Values);
}

and use it like this:

update(set, e => e.ParentID, parentID, someList, e => e.ID, somefunc);

Upvotes: 1

JotaBe
JotaBe

Reputation: 39015

First, as you've been told in the comment, you need to use Expression<Func<TEntity,int>> getId.

To use it, don't do this entities.Select(e => getID(e)) but simply this entities.Select(getID). The reason is that Select expects a lamba expression, and getId contains the lambda expression. The Expression<> is necessary so it's an expression tree necessary for EF to work, and not a compiled expression. You can read this for more information.

As to the alternative solution of an interface, you would have to implement it like this:

public interface IId
{
   public int GetId();
}

Then, you need to implement it in all your classes, for example like this:

public int GetId() { return parentId; }

or

public int GetId() { return parentId; }

You should also have to add the interface constraint to the generic method like this: where TEntity: class ,IId

However you'd find two problems:

  1. You would need to implement the GetId for all your entity types (or at least for those which will be used with thid method)
  2. The GetId function could not be translated to an expression tree, so EF wouldn't know how to convert it into a SQL expression. (I'm not sure, but it would even perhaps raise an error).

Upvotes: 1

Related Questions