用类型转换包装一个 Expression<Func<T,bool>>
Wrap a Expression<Func<T,bool>> with a type cast
我的存储库 returns 个源自共同基础的实体 class
class BaseClass
{
public int Id { get; set; }
}
class MyClass : BaseClass
{
public string Name { get; set; }
}
class MyOtherClass : BaseClass
{
...
}
在这样的函数中:
IQueryable<T> GetEntities<T>() where T : BaseClass
我添加了一种方法,使用 Expression<Func<T,bool>>
将特定实体的其他过滤器注册为 lambda,如下所示:
RegisterFilter<MyClass>(t => t.Name == "Test" );
只要在类型参数中使用 MyClass
调用 GetEntities
就会应用。
问题
如何在运行时动态创建一个表达式来包装过滤器的类型转换?
在我的特定情况下,GetEntities
是在 IQueryable<MyClass>
上调用的,使用 BaseClass
作为类型参数和事件艰难我 知道 MyClass
的过滤器需要应用我没有找到这样做的方法:
IQueryable<BaseClass> src =
(new List<MyClass>
{
new MyClass { Id = 1, Name = "asdf" },
new MyClass { Id = 2, Name = "Test" }
})
.AsQueryable();
Expression<Func<MyClass, bool>> filter = o => o.Name == "Test";
// does not work (of course)
src.Where(filter);
尝试失败
显然,我可以在调用 Where
之前将集合转换回去,但我在运行时尝试这样做没有成功:
// HACK: don't look
var genericCast = typeof(Queryable).GetMethod("Cast").MakeGenericMethod(entityType);
var genericWhere = typeof(Queryable).GetMethods().Single(mi => mi.Name == "Where" && mi.GetParameters()[1].ParameterType.GenericTypeArguments[0].Name == "Func`2");
q = (IQueryable<T>)genericCast.Invoke(q, new object[] { genericWhere.Invoke(q, new object[] { filterExp }) });
System.InvalidOperationException: 'Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.'
因为这也很丑陋,所以我试着像这样用 cast 包裹我的过滤器:
LambdaExpression filterExp = (LambdaExpression)filter;
var call = filterExp.Compile();
Expression<Func<T, bool>> wrap = o => (bool)call.DynamicInvoke(Convert.ChangeType(o, entityType));
这可行,但会阻止将过滤器生成到存储表达式中,因此它将在内存中完成,这也是不可取的。
我觉得这个问题的解决方案应该很简单,但我似乎无法正确解决,因此非常感谢您的帮助。
您可以:
// Using SimpleExpressionReplacer from
public static Expression<Func<BaseClass, bool>> Filter<TDerived>(Expression<Func<TDerived, bool>> test)
where TDerived : BaseClass
{
// x =>
var par = Expression.Parameter(typeof(BaseClass), "x");
// x is TDerived
var istest = Expression.TypeIs(par, typeof(TDerived));
// (TDerived)x
var convert = Expression.Convert(par, typeof(TDerived));
// If test is p => p.something == xxx, replace all the p with ((TDerived)x)
var test2 = new SimpleExpressionReplacer(test.Parameters[0], convert).Visit(test.Body);
// x is TDerived && test (modified to ((TDerived)x) instead of p)
var and = Expression.AndAlso(istest, test2);
// x => x is TDerived && test (modified to ((TDerived)x) instead of p)
var test3 = Expression.Lambda<Func<BaseClass, bool>>(and, par);
return test3;
}
请注意,我使用的是我在另一个答案中写的 。
给出这样的测试:
var exp = Filter<MyClass>(p => p.Name == "Test");
结果表达式是:
x => x is MyClass && ((MyClass)x).Name == "Test"
我的存储库 returns 个源自共同基础的实体 class
class BaseClass
{
public int Id { get; set; }
}
class MyClass : BaseClass
{
public string Name { get; set; }
}
class MyOtherClass : BaseClass
{
...
}
在这样的函数中:
IQueryable<T> GetEntities<T>() where T : BaseClass
我添加了一种方法,使用 Expression<Func<T,bool>>
将特定实体的其他过滤器注册为 lambda,如下所示:
RegisterFilter<MyClass>(t => t.Name == "Test" );
只要在类型参数中使用 MyClass
调用 GetEntities
就会应用。
问题
如何在运行时动态创建一个表达式来包装过滤器的类型转换?
在我的特定情况下,GetEntities
是在 IQueryable<MyClass>
上调用的,使用 BaseClass
作为类型参数和事件艰难我 知道 MyClass
的过滤器需要应用我没有找到这样做的方法:
IQueryable<BaseClass> src =
(new List<MyClass>
{
new MyClass { Id = 1, Name = "asdf" },
new MyClass { Id = 2, Name = "Test" }
})
.AsQueryable();
Expression<Func<MyClass, bool>> filter = o => o.Name == "Test";
// does not work (of course)
src.Where(filter);
尝试失败
显然,我可以在调用 Where
之前将集合转换回去,但我在运行时尝试这样做没有成功:
// HACK: don't look
var genericCast = typeof(Queryable).GetMethod("Cast").MakeGenericMethod(entityType);
var genericWhere = typeof(Queryable).GetMethods().Single(mi => mi.Name == "Where" && mi.GetParameters()[1].ParameterType.GenericTypeArguments[0].Name == "Func`2");
q = (IQueryable<T>)genericCast.Invoke(q, new object[] { genericWhere.Invoke(q, new object[] { filterExp }) });
System.InvalidOperationException: 'Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.'
因为这也很丑陋,所以我试着像这样用 cast 包裹我的过滤器:
LambdaExpression filterExp = (LambdaExpression)filter;
var call = filterExp.Compile();
Expression<Func<T, bool>> wrap = o => (bool)call.DynamicInvoke(Convert.ChangeType(o, entityType));
这可行,但会阻止将过滤器生成到存储表达式中,因此它将在内存中完成,这也是不可取的。
我觉得这个问题的解决方案应该很简单,但我似乎无法正确解决,因此非常感谢您的帮助。
您可以:
// Using SimpleExpressionReplacer from
public static Expression<Func<BaseClass, bool>> Filter<TDerived>(Expression<Func<TDerived, bool>> test)
where TDerived : BaseClass
{
// x =>
var par = Expression.Parameter(typeof(BaseClass), "x");
// x is TDerived
var istest = Expression.TypeIs(par, typeof(TDerived));
// (TDerived)x
var convert = Expression.Convert(par, typeof(TDerived));
// If test is p => p.something == xxx, replace all the p with ((TDerived)x)
var test2 = new SimpleExpressionReplacer(test.Parameters[0], convert).Visit(test.Body);
// x is TDerived && test (modified to ((TDerived)x) instead of p)
var and = Expression.AndAlso(istest, test2);
// x => x is TDerived && test (modified to ((TDerived)x) instead of p)
var test3 = Expression.Lambda<Func<BaseClass, bool>>(and, par);
return test3;
}
请注意,我使用的是我在另一个答案中写的
给出这样的测试:
var exp = Filter<MyClass>(p => p.Name == "Test");
结果表达式是:
x => x is MyClass && ((MyClass)x).Name == "Test"