这是可能的,但该方法需要访问DbContext
为了获取描述主键的元数据。然后,它可以根据该元数据和传递的值动态构建谓词 lambda 表达式。
首先,我们需要一个收集有关实体主键属性信息的方法。
对于 EF Core 来说很简单:
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
或任何其他过滤方法。