这是可能的,但该方法需要访问DbContext
以获取描述主键的元数据。然后,它可以基于该元数据和传递的值构建动态谓词lambda表达式。
首先我们需要一个方法来收集关于实体主键属性的信息。
对于EF核心很简单:
static IReadOnlyList<IProperty> GetPrimaryKeyProperties(DbContext dbContext, Type clrEntityType)
{
return dbContext.Model.FindEntityType(clrEntityType).FindPrimaryKey().Properties;
}
对于EF6它更复杂一些,但仍然是可行的:
struct KeyPropertyInfo
{
public string Name;
public Type ClrType;
}
public static IReadOnlyList<KeyPropertyInfo> GetPrimaryKeyProperties(DbContext dbContext, Type clrEntityType)
{
var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
var metadata = objectContext.MetadataWorkspace;
var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));
var entityType = metadata.GetItems<EntityType>(DataSpace.OSpace)
.Single(e => objectItemCollection.GetClrType(e) == clrEntityType);
return entityType.KeyProperties
.Select(p => new KeyPropertyInfo
{
Name = p.Name,
ClrType = p.PrimitiveType.ClrEquivalentType
})
.ToList();
}
现在建设中的谓语方法是这样的:
static Expression<Func<T, bool>> BuildKeyPredicate<T>(DbContext dbContext, object[] id)
{
var keyProperties = GetPrimaryKeyProperties(dbContext, typeof(T));
var parameter = Expression.Parameter(typeof(T), "e");
var body = keyProperties
// e => e.PK[i] == id[i]
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Convert(
Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
p.ClrType)))
.Aggregate(Expression.AndAlso);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
这里棘手的部分是如何让EF使用参数化查询。如果我们只使用Expression.Constant(id[i])
,则生成的SQL将使用常量值而不是参数。所以诀窍是使用持有该值的临时匿名类型的常量表达式(基本上模拟闭包)的成员访问表达式(即属性或字段)。
一旦从上述方法获得谓词,就可以将其用于FirstOrDefaultAsync
或任何其他过滤方法。
谢谢我急需这个 –