【问题标题】:Wrap a Expression<Func<T,bool>> with a type cast用类型转换包装 Expression<Func<T,bool>>
【发布时间】:2018-12-27 11:58:10
【问题描述】:

我的存储库返回从公共基类派生的实体

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&lt;Func&lt;T,bool&gt;&gt; 将特定实体的其他过滤器注册为 lambda,如下所示:

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

只要在类型参数中使用MyClass 调用GetEntities,就会应用它。

问题

如何在运行时动态创建一个表达式,将类型转换包装在过滤器周围?

在我的特定情况下,GetEntities 使用 BaseClass 作为类型参数和事件艰难调用 IQueryable&lt;MyClass&gt;知道需要应用 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: '不能对 ContainsGenericParameters 为 true 的类型或方法执行后期绑定操作。'

因为这也很丑陋,所以我尝试将过滤器包装成这样:

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

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

这可行,但会阻止将过滤器生成到存储表达式中,因此它将在内存中完成,这也是不可取的。

我觉得这个问题的解决方案应该是微不足道的,但我似乎无法正确解决,因此非常感谢任何帮助。

【问题讨论】:

  • 你为什么不能Expression&lt;Func&lt;BaseClass, bool&gt;&gt; o =&gt; o is MyClass &amp;&amp; (MyClass)o).Name == "Test";

标签: c# generics lambda iqueryable


【解决方案1】:

你可以:

// 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;
}

请注意,我使用的是我在另一个答案中写的SimpleExpressionReplacer

给定一个类似的测试:

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

结果表达式为:

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

【讨论】:

  • 用这个表达式语法可以做什么总是令人着迷。只要你相信,一切皆有可能!注意:OP 甚至没有询问 TypeIs()-Part,但这可以防止异常和可能的误用。
  • 哥们,你是一个真正的表情英雄!可靠的答案-如果可以的话,我会投票一百万次
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-16
  • 2021-06-17
相关资源
最近更新 更多