使用 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
但是你会发现两个问题:
- 您需要为所有实体类型(或至少为那些将与 thid 方法一起使用的实体类型)实施
GetId
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);
我想创建基于 Entity Framework 的函数,它将更新 SQL 数据库中的 Table。
这 table 有 parent-child 关系,所以我正在根据 parentID
.
我想要一个通用函数,但我不知道如何从 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
但是你会发现两个问题:
- 您需要为所有实体类型(或至少为那些将与 thid 方法一起使用的实体类型)实施
GetId
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);