使用 Linq to Entity 中的 Func 作为泛型方法参数

Use Func in Linq to Entity as generic method parameter

我想创建基于 Entity Framework 的函数,它将更新 SQL 数据库中的 Table。

这 table 有 parent-child 关系,所以我正在根据 parentID.

更新那一堆 children

我想要一个通用函数,但我不知道如何从 object 中获取 ID,其定义在编译时未知。

我尝试使用 Func 这可能非常简洁,但它不能用于 Linq-To-Entities。

代码:

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

我想像这样使用这个功能:

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

IMO 非常简洁。有什么办法可以实现这样的功能吗?

我做不到(我试过了):

ID为属性的界面可以很整洁。但是我无法在此界面中定义 parentID,因为此 属性 的名称因数据库中的实体而异。

首先,如评论中所述,您需要使用 Expression<Func<TEntity,int>> getId

要使用它,请不要这样做 entities.Select(e => getID(e)),只需这样做 entities.Select(getID)。原因是 Select 需要一个 lamba 表达式,而 getId 包含 lambda 表达式。 Expression<> 是必需的,因此它是 EF 工作所必需的表达式树,而不是已编译的表达式。你可以阅读 this for more information.

至于接口的替代解决方案,您将不得不像这样实现它:

public interface IId
{
   public int GetId();
}

然后,您需要在所有 类 中实现它,例如:

public int GetId() { return parentId; }

public int GetId() { return parentId; }

您还应该像这样向泛型方法添加接口约束:where TEntity: class ,IId

但是你会发现两个问题:

  1. 您需要为所有实体类型(或至少为那些将与 thid 方法一起使用的实体类型)实施 GetId
  2. GetId 函数无法转换为表达式树,因此 EF 不知道如何将其转换为 SQL 表达式。 (我不确定,但它甚至可能会引发错误)。

我认为你的函数应该是这样的:

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

并像这样使用它:

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