简体   繁体   English

包装表达式 <Func<T,bool> &gt;使用类型转换

[英]Wrap a Expression<Func<T,bool>> with a type cast

My repository returns entities derived from a common base class 我的存储库返回从通用基类派生的实体

class BaseClass
{
    public int Id { get; set; }
}

class MyClass : BaseClass
{
    public string Name { get; set; }
}

class MyOtherClass : BaseClass
{
    ...
}

in a function like this: 在这样的函数中:

IQueryable<T> GetEntities<T>() where T : BaseClass

I added a method to register additional filters for specific entities as lambdas using Expression<Func<T,bool>> like this: 我添加了一种方法,可以使用Expression<Func<T,bool>>特定实体的其他过滤器注册为lambda Expression<Func<T,bool>>如下所示:

RegisterFilter<MyClass>(t => t.Name == "Test" );

that will be applied whenever GetEntities is called with MyClass in the type argument. 只要在type参数中使用MyClass调用GetEntities时,将应用该函数。

Question

How can I create an expression dynamically at runtime that wraps a type cast around the filter? 如何在运行时动态创建一个将类型转换包装在过滤器周围的表达式?

in my specific case GetEntities is called on an IQueryable<MyClass> using the BaseClass as type argument and event tough I know that the filter for MyClass needs to applied I did not find a way to do so: 在我的特定情况下,使用BaseClass作为类型实GetEntitiesIQueryable<MyClass>上调用GetEntities并且很难处理事件,我知道 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);

Failed attempts 尝试失败

Obviously I could cast the collection back before calling Where but my attempts to do this at runtime did not work: 显然,我可以在调用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.' System.InvalidOperationException:'无法对包含ContainsGenericParameters为true的类型或方法执行后期绑定操作。

since this is also very ugly I tried to wrap my filter in cast like this: 由于这也很丑陋,因此我尝试将滤镜包装在演员表中,如下所示:

LambdaExpression filterExp = (LambdaExpression)filter;
var call = filterExp.Compile();

Expression<Func<T, bool>> wrap = o => (bool)call.DynamicInvoke(Convert.ChangeType(o, entityType));

This works but prevents the filter to be generated into a store expression so it will be done in memory which is also undesirable. 这是可行的,但阻止将过滤器生成到存储表达式中,因此它将在内存中完成,这也是不希望的。

I feel like the solution to this should be trivial but I can't seem to get it right so any help would be appreciated very much. 我觉得解决这个问题应该是微不足道的,但是我似乎做得不好,因此非常感谢您的帮助。

You can: 您可以:

// Using SimpleExpressionReplacer from https://stackoverflow.com/a/29467969/613130
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;
}

Note that I'm using the SimpleExpressionReplacer I wrote in another answer. 请注意,我正在使用在另一个答案中编写的SimpleExpressionReplacer

Given a test like: 给定一个像这样的测试:

var exp = Filter<MyClass>(p => p.Name == "Test");

the resulting expression is: 结果表达式为:

x => x is MyClass && ((MyClass)x).Name == "Test"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM