joh*_*y 5 2 c# entity-framework query-cache linq-expressions .net-core
我有一个函数生成一个表达式来过滤一个表的主键,当传入时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);
}
Run Code Online (Sandbox Code Playgroud)
这通过首先获取表的主键,它创建二进制表达式foreach属性,Id以匿名类型包装以利用查询缓存.这工作正常.但是,我想更进一步.
我想保留Expression,所以每次传递一组新的id时我都不必生成它,如何Expression在仍然利用查询缓存的同时存储它?
编辑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);
}
}
Run Code Online (Sandbox Code Playgroud)
类型'<> f__AnonymousType0`1 [System.Object]'和'System.Int32'之间没有定义强制运算符
我越来越近,但我不确定它是否会继续工作.
正如我在评论中提到的,主要问题是我们不能在表达式树中使用数组索引访问 - 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<,,,,,,,>),
};
}
Run Code Online (Sandbox Code Playgroud)
您可以创建和存储该类的实例.然后使用Predicate查询中属性返回的表达式.以及SetValues设置参数的方法.
缺点是值存储绑定到类实例,因此不能同时使用它.最初的方法适用于所有情况,IMO的性能影响应该可以忽略不计,因此您可以考虑坚持下去.