Translating a lambda expression into an expression tree
The real world problem I'm trying to solve: I have a database with a bunch of phone numbers stored as strings in an absolutely terrible format (eg "(02) 9971 1209"). The user of my program is going to start typing a phone number and as he types, what he is typing will be sent off and used to filter the list of phone numbers in the database using a "startswith" operation.
The problem is that if the user types "02997", it's not going to match anything because the numbers are stored with the location code enclosed in brackets. In order for this operation to work, the user would have to start every search by typing '('. This is not good.
tl;dr version at the bottom of the question
I took the problem out of it's real world context and into a smaller, sealed solution where I can focus on it without the distractions of a massive codebase. Under such conditions, my solution was to do some linq trickiness:
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);
}
}
}
This is all good, works perfectly, does exactly what I need. But it only works in this isolated context: I can't port it back to the codebase where it needs to be implemented because I need to translate it to an expression tree.
The code where I need to wack it down:
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
I need to take propertyExpression, and somehow embed the property it is talking about 开发者_开发知识库(in this case the table of phone numbers in the external db) in my lambda function. Then I need to decompile the whole thing back to an Expression Tree and return that.
That's where I'm stuck. The best I can come up with is a super long and inelegant line of linq that doesn't even have the right type anyway:
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)));
I wanted to do something like this, which still doesn't solve the problem but is slightly neater:
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)));
I've tried applying lots of little alterations to it but it refuses to compile.
tl;dr:
Long story short, what I need to do is take an expression representing a field on a table in a database and transform that expression into one that represents the same thing, but with a filter applied that strips all characters that aren't digits.
A lot of the expressions you'd want to use to get the stripped version of the phone numbers probably won't be supported by your LINQ provider.
I'd personally suggest making a database View that shows all the phone numbers in their stripped-down format. Then you can just do a standard StartsWith
query against that view.
精彩评论