将lambda表达式转换为表达式树

时间:2011-09-28 01:21:00

标签: c# .net linq expression-trees

我正在尝试解决的现实世界问题:我有一个数据库,其中一堆电话号码以绝对可怕的格式存储为字符串(例如“(02)9971 1209”)。我的程序的用户将开始输入电话号码,当他输入时,他将输入的内容将被发送并用于使用“startswith”操作过滤数据库中的电话号码列表。

问题在于,如果用户输入“02997”,则不会匹配任何内容,因为这些数字与括在括号中的位置代码一起存储。为了使此操作起作用,用户必须通过键入'('来开始每次搜索。这不是很好。

tl;问题底部的博士版

我把这个问题摆脱了现实世界的背景,进入了一个更小,更密集的解决方案,我可以专注于它而不会分散大量的代码库。在这种情况下,我的解决方案是做一些linq棘手:

class Program
{
    static string [] phones = {"(02) 9489 3048","(04) 1128 2148","(01) 9971 1208",};

    static void Main(string[] args)
    {
        for (;;)
        {
            Console.WriteLine("Enter your number: ");
            string input = Console.ReadLine();

            Func<string, string> strip = 
            tehstring => tehstring.Where(x => char.IsDigit(x))
                                  .Aggregate("", (x, y) => x + y);
            var results = phones.Where(z => strip(z).StartsWith(strip(input)));

            foreach (var x in results)
                Console.WriteLine(x);
        }
    }
}

这一切都很好,效果很好,完全符合我的需要。但它只适用于这个孤立的上下文:我无法将其移回需要实现的代码库,因为我需要将其转换为表达式树。

我需要解决的代码:

static Expression GetOperationExpression(
  StringFilterOperation operation, 
  Expression propertyExpression, 
  Expression valueExpression)
{
  switch (operation)
  {
    case StringFilterOperation.StartsWith:
    var startsWith = typeof(string)
                     .GetMethod("StartsWith", new Type[] { typeof(string) });
    Contract.Assume(startsWith != null);
    return Expression.Call(propertyExpression, startsWith, valueExpression);
    //etc etc etc

我需要使用propertyExpression,并以某种方式在我的lambda函数中嵌入它正在讨论的属性(在这种情况下是外部数据库中的电话号码表)。然后我需要将整个事物反编译回表达式树并返回它。

那就是我被困住的地方。我能想出的最好的是一条超长而且不优雅的linq系列,它甚至都没有合适的类型:

Expression<Func<string[],string, IEnumerable<string>>> expr = 
(db, uinput) => db.Where(z => z.Where(x => char.IsDigit(x))
                               .Aggregate("", (x, y) => x + y)
                               .StartsWith(uinput.Where(x => char.IsDigit(x))
                                                 .Aggregate("",(x,y) => x + y)));

我想做这样的事情,但仍然没有解决问题,但是有点整洁:

Expression<Func<string, string>> strip = 
tehstring => tehstring.Where(x => char.IsDigit(x))
                      .Aggregate("", (x, y) => x + y);
Expression<Func<string, string, string>> query = 
(db, uinput) => db.Where(z => strip(z)
                  .StartsWith(strip(uinput)));

我尝试过对它进行大量的改动,但它拒绝编译。

TL; DR:

长话短说,我需要做的是在数据库中的表上使用表示字段的表达式,并将该表达式转换为表示相同内容的表达式,但应用了一个过滤器,用于删除所有不是位数。

1 个答案:

答案 0 :(得分:5)

您的LINQ提供商可能不支持您希望用来获取电话号码的剥离版本的许多表达式。

我个人建议制作一个数据库视图,以其精简格式显示所有电话号码。然后,您可以针对该视图执行标准StartsWith查询。