Сохранить статический фильтр по выражению ключа - PullRequest
0 голосов
/ 27 июня 2018

У меня есть функция, которая генерирует выражение для фильтрации таблицы по ее первичному ключу, когда передается в Object[], это очень похоже на функцию Find, за исключением того, что она не материализуется, поэтому вы можете передать IQueryable вокруг потом

public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
{
    var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
    var parameter = Expression.Parameter(typeof(T), "e");
    var body = keyProperties
        // e => e.{propertyName} == new {id = id[i]}.id
        .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);
}

Сначала работает получение первичных ключей для таблицы, создается свойство foreach для двоичного выражения, идентификатор оборачивается в анонимный тип для использования кэша запросов. Это работает нормально. Тем не менее, я хотел бы сделать еще один шаг вперед.

Я бы хотел сохранить Expression, чтобы мне не приходилось генерировать его каждый раз, когда я передаю новый набор идентификаторов. Как я могу сохранить это Expression, все еще используя Query Cache?

Редактировать TL; DR

Итак, я пытаюсь кешировать его с помощью доступа к массиву в статическом классе, как предлагается, однако я сталкиваюсь с ошибкой:

public class PrimaryKeyFilterContainer<T>
{
    const string ANON_ID_PROP = "id";
    static Expression<Func<T, bool>> _filter;
    Type ANON_TYPE = new { id = (object)0 }.GetType();
    public object[] id { get; set; }

    public PrimaryKeyFilterContainer()
    {
    }

    public Expression<Func<T, bool>> GetFilter(DbContext dbContext, object[] id)
    {
        this.id = id;

        if(null == _filter)
        {
            var keyProperties = dbContext.GetPrimaryKeyProperties<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(BuildNewExpression(i),
                        p.ClrType)))
                .Aggregate(Expression.AndAlso);

            _filter = Expression.Lambda<Func<T, bool>>(body, parameter);
        }

        return _filter;
    }

    NewExpression BuildNewExpression(int index)
    {
        var currentObject = Expression.Constant(this);
        var fieldAccess = Expression.PropertyOrField(currentObject, nameof(id));
        var arrayAccess = Expression.ArrayAccess(fieldAccess, Expression.Constant(index));
        return Expression.New(ANON_TYPE.GetConstructor(new[] { typeof(object) }), arrayAccess);
    }
}

Не определен оператор приведения между типами '<> f__AnonymousType0`1 [System.Object]' и 'System.Int32'

Я становлюсь ближе, но я не уверен, будет ли это работать до сих пор.

1 Ответ

0 голосов
/ 27 июня 2018

Как я уже упоминал в комментариях, основная проблема заключается в том, что мы не можем использовать доступ к индексу массива внутри дерева выражений - 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 на производительность должно быть незначительным, поэтому вы можете остановиться на нем.

...