按键表达存储静态filter

我有一个函数生成一个表达式来过滤一个表的主键,当它在Object[]传递时,这与Find函数非常相似,只是它没有实现,所以你可以在之后传递一个IQueryable

 public static Expression<Func> FilterByPrimaryKeyPredicate(this DbContext dbContext, object[] id) { var keyProperties = dbContext.GetPrimaryKeyProperties(); 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>(body, parameter); } 

这首先获取表的主键,它创建二进制表达式foreach属性,Id以匿名类型包装以利用查询缓存。 这工作正常。 但是,我想更进一步。

我想保留Expression,所以每次传递一组新的id时我都不必生成它,如何在仍然利用查询缓存的同时存储此Expression

编辑TL; DR

所以我试图在静态类中使用数组访问来缓存它,如我所知,但是我遇到了一个错误:

 public class PrimaryKeyFilterContainer { const string ANON_ID_PROP = "id"; static Expression<Func> _filter; Type ANON_TYPE = new { id = (object)0 }.GetType(); public object[] id { get; set; } public PrimaryKeyFilterContainer() { } public Expression<Func> GetFilter(DbContext dbContext, object[] id) { this.id = id; if(null == _filter) { var keyProperties = dbContext.GetPrimaryKeyProperties(); 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>(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’之间没有定义强制运算符

我越来越近但我不确定它是否会继续工作。

正如我在评论中提到的,主要问题是我们不能在表达式树中使用数组索引访问 – EF6抛出不支持的exception,EF Core将其转换为客户端评估。

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

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

 public class PrimaryKeyFilter where TEntity : class { object valueBuffer; Func valueArrayConverter; public PrimaryKeyFilter(DbContext dbContext) { var keyProperties = dbContext.GetPrimaryKeyProperties(); // 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>(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>(body, parameter); } } public Expression> 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的性能影响应该可以忽略不计,因此您可以考虑坚持下去。