Возможно, но для доступа к метаданным, описывающим первичный ключ, необходимо получить доступ к DbContext
. Затем он может создавать динамически предикативное выражение лямбда на основе этих метаданных и переданных значений.
Сначала нам нужен метод, который собирает информацию о свойствах первичного ключа сущности.
Для 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
или любого другого метода фильтрации.
Спасибо, что я отчаянно нуждался в этом –