通过键表达式存储静态过滤器 [英] Store Static Filter By Key Expression

查看:88
本文介绍了通过键表达式存储静态过滤器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个函数,该函数生成一个表达式以通过其主键过滤表,当传入 Object [] 时,这与<$非常相似c $ c>查找函数,但是它没有实现,因此您可以在之后传递 IQueryable

I've got an function which generates an expression to filter a table by it's primary key, when passed in an Object[], this is very similar to Find function except that it doesn't materialize so you can pass an IQueryable around afterwards

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属性中,Id被包装为匿名类型以利用查询缓存。一切正常。但是,我想更进一步。

This works by first getting the primary keys for a table, it creates binary expression foreach property, the Id is wrapped in an anonymous type to leverage the query cache. This is working fine. However, I'd like to take this a step further.

我想保留表达式,这样我每次传递一组新ID时都不必生成该表达式,如何存储此表达式仍在利用查询缓存吗?

I'd like to preserve the Expression so I don't have to generate it each time I pass on a new set of ids, How can I store this Expression while still leveraging the Query Cache?

编辑TL; DR

因此,我尝试按照建议在静态类中使用数组访问来对其进行缓存,但是我遇到了一个错误:

So I'm attempt to cache it using array access in a static class as suggest, however I'm encountering an error:

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'

No coercion operator is defined between types '<>f__AnonymousType0`1[System.Object]' and 'System.Int32'

但我不确定它是否还会继续工作。

I'm getting closer but I'm not sure if it's going to work still.

推荐答案

正如我在评论中提到的,主要问题是我们不能在表达式树内使用数组索引访问-EF6抛出

As I mentioned in the comments, the main problem is that we cannot use array index access inside the expression tree - EF6 throws not supported exception and EF Core turns it into client evaluation.

因此,我们需要将键存储在具有属性和属性类型动态计数的类中。幸运的是, System.Tuple 泛型类提供了此类功能,并且可以在EF6和EF Core中使用。

So we need to store the keys in a class with dynamic count of properties and property types. Fortunately the System.Tuple generic classes provide such functionality, and can be used in both EF6 and EF Core.

以下是实现上述想法的类:

Following is a class that implements the above idea:

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 方法来设置参数。

You can create and store an instance of the class. Then use the expression returned by the Predicate property inside the query. And SetValues method to set the parameters.

缺点是值存储绑定到类实例,因此不能同时使用。原始方法在所有情况下都适用,并且IMO对性能的影响应该可以忽略不计,因此您可以考虑继续使用它。

The drawback is that the value storage is bound to the class instance, hence it cannot be used concurrently. The original approach works well in all scenarios, and the performance impact IMO should be negligible, so you might consider staying on it.

这篇关于通过键表达式存储静态过滤器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆