如何在动态LINQ中使用动态Lambda [英] How can I make it possible to use a dynamic Lambda in Dynamic LINQ

查看:105
本文介绍了如何在动态LINQ中使用动态Lambda的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我搜索表达式解析器时,我发现动态LINQ API 。我想使用这个API让最终用户指定一些用于验证业务对象的标准。



所以我第一次尝试使用库来完成以下单元测试

  var x = Expression.Parameter(typeof(int),x); 
var list = Expression.Parameter(typeof(List< int>),list);
var e = DynamicExpression.ParseLambda(new [] {x,list},null,list.Any(it == x));
var compiledExpression = e.Compile();

var myList = new List< int> {24,46,67,78};
Assert.AreEqual(false,compiledExpression.DynamicInvoke(2,myList));
Assert.AreEqual(true,compiledExpression.DynamicInvoke(24,myList));

然而,我想要稍微更复杂的语法,因为我想更改这个

  list.Any(it == x)// OK 

into

  list.Any(i => i == x)//引发错误:类型'int'中没有属性或字段'i'

然而,第二种语法允许我嵌套lambda(这是我的最终目标),如下所示:

  list1.All(i => list2.Any (j => j == i))

任何人都知道如何调整Dynamic.cs来支持这个语法?

解决方案

经过几个小时的调试,我自己找到了解决方案。



以下单元测试成功:

  var list1 = Expression.Parameter(typeof(List< int> 列表1); 
var list2 = Expression.Parameter(typeof(List< int>),list2);
var e = DynamicExpression.ParseLambda(new [] {list1,list2},null,list2.All(i => list1.Any(j => j == i)));
var compiledExpression = e.Compile();

var myList1 = new List< int> {24,46,67,78};
var myList2 = new List< int> {46};
var myList3 = new List< int> {8};
Assert.AreEqual(true,compiledExpression.DynamicInvoke(myList1,myList2));
Assert.AreEqual(false,compiledExpression.DynamicInvoke(myList1,myList3));

我应用于示例Dynamic.cs文件的更改:



1)使用成员Lambda扩展枚举TokenId

2)将名为内部的IDictionary添加到ExpressionParser类中。在ExpressionParser构造函数中初始化



3)替换(从第971行开始)

  if(symbols.TryGetValue(token.text,out value)|| 
externals!= null&& externals.TryGetValue(token.text,out value)){

  if symbol.TryGetValue(token.text,out value)|| 
externals!= null&& externals.TryGetValue(token.text,out value)||
internals.TryGetValue(token.text,出价值)){

4)替换(从第1151行开始)

  if(member == null)
throw ParseError(errorPos,Res.UnknownPropertyOrField,
id,GetTypeName(type));

  if(member == null)
{
if(token.id == TokenId.Lambda&& it.Type == type)
{
//这可能是在lambda表达式中使用的内部变量,因此可以像这样存储
internals.Add(id,it);
NextToken();
var right = ParseExpression();
return right;
}
else
{
throw ParseError(errorPos,Res.UnknownPropertyOrField,
id,GetTypeName(type));
}
}

5)替换(从1838开始) p>

  case'=':
NextChar();
if(ch =='='){
NextChar();
t = TokenId.DoubleEqual;
}
else {
t = TokenId.Equal;
}
break;

  case'=':
NextChar();
if(ch =='='){
NextChar();
t = TokenId.DoubleEqual;
}
else if(ch =='>')
{
NextChar();
t = TokenId.Lambda;
}
else {
t = TokenId.Equal;
}
break;


In my search for a expression parser, I found the Dynamic LINQ API. I want to use this API for letting the end user specify some criteria for validation of the business objects.

So my first attempt to use the library succeeds with the following Unit test

var x = Expression.Parameter(typeof(int), "x");
var list = Expression.Parameter(typeof(List<int>), "list");
var e = DynamicExpression.ParseLambda(new[] { x, list }, null, "list.Any(it == x)");
var compiledExpression = e.Compile();

var myList = new List<int> { 24, 46, 67, 78 };
Assert.AreEqual(false, compiledExpression.DynamicInvoke(2, myList));
Assert.AreEqual(true, compiledExpression.DynamicInvoke(24, myList));

However I want to have a slightly more complex syntax as I want to change this

list.Any(it == x)     // OK

into

list.Any(i => i == x) // Raises error: No property or field 'i' exists in type 'int'

The second syntax would however allow me to nest lambda's (which is my ultimate goal) like so:

list1.All(i => list2.Any(j => j == i))

Anyone know how to adjust Dynamic.cs to support this syntax?

解决方案

After some hours of debugging I found the solution myself.

The following unit test succeeds now:

var list1 = Expression.Parameter(typeof(List<int>), "list1");
var list2 = Expression.Parameter(typeof(List<int>), "list2");
var e = DynamicExpression.ParseLambda(new[] { list1, list2 }, null, "list2.All(i => list1.Any(j => j == i))");
var compiledExpression = e.Compile();

var myList1 = new List<int> { 24, 46, 67, 78 };
var myList2 = new List<int> { 46 };
var myList3 = new List<int> { 8 };
Assert.AreEqual(true, compiledExpression.DynamicInvoke(myList1, myList2));
Assert.AreEqual(false, compiledExpression.DynamicInvoke(myList1, myList3));

The changes I have applied to the example Dynamic.cs file:

1) Extend the enum TokenId with the member 'Lambda'

2) Add an IDictionary named internals to class ExpressionParser. Initialize it in the ExpressionParser constructor

3) Replace (starting on line 971)

if (symbols.TryGetValue(token.text, out value) ||
    externals != null && externals.TryGetValue(token.text, out value)) {

with

if (symbols.TryGetValue(token.text, out value) ||
externals != null && externals.TryGetValue(token.text, out value) ||
internals.TryGetValue(token.text, out value)) {

4) Replace (starting on line 1151)

if (member == null)
    throw ParseError(errorPos, Res.UnknownPropertyOrField,
       id, GetTypeName(type));

with

if (member == null)
{
    if(token.id == TokenId.Lambda && it.Type == type)
    {
        // This might be an internal variable for use within a lambda expression, so store it as such
        internals.Add(id, it);
        NextToken();
        var right = ParseExpression();
        return right;
    }
    else
    {
        throw ParseError(errorPos, Res.UnknownPropertyOrField,
            id, GetTypeName(type));                        
    }
}

5) Replace (starting on line 1838)

case '=':
NextChar();
if (ch == '=') {
    NextChar();
    t = TokenId.DoubleEqual;
}
else {
    t = TokenId.Equal;
}
break;

with

case '=':
NextChar();
if (ch == '=') {
    NextChar();
    t = TokenId.DoubleEqual;
}
else if(ch == '>')
{
    NextChar();
    t = TokenId.Lambda;
}
else {
    t = TokenId.Equal;
}
break;

这篇关于如何在动态LINQ中使用动态Lambda的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆