Как я уже упоминал в комментариях, основная проблема заключается в том, что мы не можем использовать доступ к индексу массива внутри дерева выражений - EF6 генерирует неподдерживаемое исключение, а EF Core превращает его в оценку клиента.
Таким образом, нам нужно хранить ключи в классе с динамическим количеством свойств и типов свойств. К счастью, универсальные классы System.Tuple
предоставляют такую функциональность и могут использоваться как в EF6, так и в EF Core.
Ниже приведен класс, который реализует вышеуказанную идею:
public class PrimaryKeyFilter<TEntity>
where TEntity : class
{
object valueBuffer;
Func<object[], object> valueArrayConverter;
public PrimaryKeyFilter(DbContext dbContext)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<TEntity>();
// Create value buffer type (Tuple) from key properties
var valueBufferType = TupleTypes[keyProperties.Count - 1]
.MakeGenericType(keyProperties.Select(p => p.ClrType).ToArray());
// Build the delegate for converting value array to value buffer
{
// object[] values => new Tuple(values[0], values[1], ...)
var parameter = Expression.Parameter(typeof(object[]), "values");
var body = Expression.New(
valueBufferType.GetConstructors().Single(),
keyProperties.Select((p, i) => Expression.Convert(
Expression.ArrayIndex(parameter, Expression.Constant(i)),
p.ClrType)));
valueArrayConverter = Expression.Lambda<Func<object[], object>>(body, parameter).Compile();
}
// Build the predicate expression
{
var parameter = Expression.Parameter(typeof(TEntity), "e");
var valueBuffer = Expression.Convert(
Expression.Field(Expression.Constant(this), nameof(this.valueBuffer)),
valueBufferType);
var body = keyProperties
// e => e.{propertyName} == valueBuffer.Item{i + 1}
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Property(valueBuffer, $"Item{i + 1}")))
.Aggregate(Expression.AndAlso);
Predicate = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
}
}
public Expression<Func<TEntity, bool>> Predicate { get; }
public void SetValues(params object[] values) =>
valueBuffer = valueArrayConverter(values);
static readonly Type[] TupleTypes =
{
typeof(Tuple<>),
typeof(Tuple<,>),
typeof(Tuple<,,>),
typeof(Tuple<,,,>),
typeof(Tuple<,,,,>),
typeof(Tuple<,,,,,>),
typeof(Tuple<,,,,,,>),
typeof(Tuple<,,,,,,,>),
};
}
Вы можете создать и сохранить экземпляр класса. Затем используйте выражение, возвращаемое свойством Predicate
внутри запроса. И SetValues
метод для установки параметров.
Недостатком является то, что хранилище значений привязано к экземпляру класса, поэтому его нельзя использовать одновременно. Первоначальный подход хорошо работает во всех сценариях, и влияние IMO на производительность должно быть незначительным, поэтому вы можете остановиться на нем.