views:

350

answers:

3

Hi, I'm using the Entity Framework and I developed this extension method:

public static IQueryable<TResult> Like<TResult>(this IQueryable<TResult> query, Expression<Func<TResult, string>> field, string value) 
{
    var expression = Expression.Lambda<Func<TResult, bool>>(
        Expression.Call(field.Body, typeof(string).GetMethod("Contains"),
        Expression.Constant(value)), field.Parameters);

    return query.Where(expression);
}

this code work correctly if I use it like this:

var result = from e in context.es.Like(r => r.Field, "xxx")
             select e

Now I need to call this extension method programmatically:

public static IQueryable<TSource> SearchInText<TSource>(this IQueryable<TSource> source, string textToFind)
{
    // Collect fields
    PropertyInfo[] propertiesInfo = source.ElementType.GetProperties();
    List<string> fields = new List<string>();
    foreach (PropertyInfo propertyInfo in propertiesInfo)
    {
        if (
            (propertyInfo.PropertyType == typeof(string)) ||
            (propertyInfo.PropertyType == typeof(int)) ||
            (propertyInfo.PropertyType == typeof(long)) ||
            (propertyInfo.PropertyType == typeof(byte)) ||
            (propertyInfo.PropertyType == typeof(short))
            )
        {
            fields.Add(propertyInfo.Name);
        }
    }

    ParameterExpression parameter = Expression.Parameter(typeof(TSource), source.ElementType.Name);
    Expression expression = Expression.Lambda(Expression.Property(parameter, typeof(TSource).GetProperty(fields[0])), parameter);
    Expression<Func<TSource, string>> field = Expression.Lambda<Func<TSource, string>>(expression, parameter);

    return source.Like(field, textToFind);
}

Now this code doesn't work! I need to understand how to declare the "field" of the Like extended methods.

Expression<Func<TSource, string>> field = Expression.Lambda<Func<TSource, string>>(expression, parameter);

At runtime I receive this error: Impossibile utilizzare un'espressione di tipo 'System.Func`2[TestMdf.Equipment,System.String]' per un tipo restituito 'System.String'

A: 

I assume your second code snippet is just a truncated example - if you really did that, then the results would be unpredictable, because you're taking the first property returned by reflection, which can change between runs of your program.

You'll get better answers if you say "This worked" followed by a description of what happened, and "This didn't work" followed by a description of how you could tell that it didn't work (compiler error? runtime error? exception message?)

Firstly, are you aware of Dynamic Linq? It allows you to delay decisions about how a query should be structured until runtime and may solve many of your problems for you.

But assuming this is a learning exercise...

Your Like extension method takes an expression (which a caller ought to usually write out as a lambda, as that's the whole point of these things). That expression will convert a "record" from a query result set and return a string value (presumably selecting it from the data stored in the record). The method also takes a value string.

But it then constructs (by hand) its own predicate that calls the Contains method on the body of the field lambda.

I guess this ought to work, because the result of that lambda is a string. However, I can't see why you're doing this the hard way. What's wrong with:

var result = from e in context.es
             where e.Field.Contains("xxx"))
             select e
Daniel Earwicker
Hi, I'd like to search in all fields so you have to compose the fields like this ' ' + F1 + ' ' + F2 ... I need an extended methods just because I don't know the field list.You are right, my second code snippet is just a truncated example.I think that this extended method can be very useful so I hope I can found some help to complete it.
Massimiliano Papaleo
My first extended methods is the only way that I found to call dinamically the Contains methods.I need to call it dinamically because I have to build the field to compare. Something like (" " + F1 + " " + F2 + " ").Contains(" " + <word1> + " ")
Massimiliano Papaleo
I need to understand how I have to build the parameter to call the Like method.If you write in your code field.Like("text") this is simple. But I don't know how I can call it dinamically.About the "doesn't work": it compiles but I recieve this error on runtime: "Impossibile utilizzare un'espressione di tipo 'System.Func`2[TestMdf.Equipment,System.String]' per un tipo restituito 'System.String'"
Massimiliano Papaleo
A: 

Now I found a partial solution to my problem:

public static IQueryable<TSource> SearchInText<TSource>(this IQueryable<TSource> source, string textToFind)
{
    // Collect fields
    PropertyInfo[] propertiesInfo = source.ElementType.GetProperties();
    List<string> fields = new List<string>();
    foreach (PropertyInfo propertyInfo in propertiesInfo)
    {
        if (
            (propertyInfo.PropertyType == typeof(string)) ||
            (propertyInfo.PropertyType == typeof(int)) ||
            (propertyInfo.PropertyType == typeof(long)) ||
            (propertyInfo.PropertyType == typeof(byte)) ||
            (propertyInfo.PropertyType == typeof(short))
            )
        {
            fields.Add(propertyInfo.Name);
        }
    }

    ParameterExpression parameter = Expression.Parameter(typeof(TSource),     source.ElementType.Name);

    var property = typeof(TSource).GetProperty(fields[0]);
    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
        var constantValue = Expression.Constant(textToFind);
    var equality = Expression.Call(Expression.Call(Expression.Property(parameter,     property), "ToUpper", null, null), typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), Expression.Constant(textToFind.ToUpper()));

    return source.Where(Expression.Lambda<Func<TSource, bool>>(equality, parameter));
}

Now the next step is to concatenate all the field list:

" " + fields[0] + " " + ... fields[n]

Some ideas?

Massimiliano Papaleo
Field concatenation? You probably want to use a StringBuilder.
John Fisher
A: 

This is my first release:

public static IQueryable<TSource> SearchInText<TSource>(this IQueryable<TSource> source, string textToFind)
{
    if (textToFind.Trim() == "")
    {
        return source;
    }
    string[] textToFindList = textToFind.Replace("'", "''").Split(' ');

    // Collect fields
    PropertyInfo[] propertiesInfo = source.ElementType.GetProperties();
    List<string> fieldList = new List<string>();
    foreach (PropertyInfo propertyInfo in propertiesInfo)
    {
        if (
            (propertyInfo.PropertyType == typeof(string)) ||
            (propertyInfo.PropertyType == typeof(int)) ||
            (propertyInfo.PropertyType == typeof(long)) ||
            (propertyInfo.PropertyType == typeof(byte)) ||
            (propertyInfo.PropertyType == typeof(short))
            )
        {
            fieldList.Add(propertyInfo.Name);
        }
    }

    ParameterExpression parameter = Expression.Parameter(typeof(TSource), source.ElementType.Name);
    MethodInfo concatMethod = typeof(String).GetMethod("Concat", new Type[] { typeof(string), typeof(string) });

    var spaceExpression = Expression.Constant(" ");
    var concatenatedField = BinaryExpression.Add(spaceExpression, Expression.MakeMemberAccess(parameter, typeof(TSource).GetProperty(fieldList[0])), concatMethod);

    for (int i = 1; i < fieldList.Count; i++)
    {
        concatenatedField = BinaryExpression.Add(concatenatedField, spaceExpression, concatMethod);
        concatenatedField = BinaryExpression.Add(concatenatedField, Expression.MakeMemberAccess(parameter, typeof(TSource).GetProperty(fieldList[i])), concatMethod);
    }

    concatenatedField = BinaryExpression.Add(concatenatedField, spaceExpression, concatMethod);
    var fieldsExpression = Expression.Call(concatenatedField, "ToUpper", null, null);

    var clauseExpression = Expression.Call(
        fieldsExpression, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }),
        Expression.Constant(textToFindList[0].ToUpper())
        );

    if (textToFindList.Length == 1)
    {
       return source.Where(Expression.Lambda<Func<TSource, bool>>(clauseExpression, parameter));
    }

    BinaryExpression expression = Expression.And(Expression.Call(
            fieldsExpression, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }),
            Expression.Constant(textToFindList[1].ToUpper())
            ), clauseExpression);
    for (int i = 2; i < textToFindList.Length; i++)
    {
        expression = Expression.And(Expression.Call(
            fieldsExpression, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }),
            Expression.Constant(textToFindList[i].ToUpper())
            ), expression);
    }

    return source.Where(Expression.Lambda<Func<TSource, bool>>(expression, parameter));

}

I will modify to manage some rules like "phrase" + and - operator.

Massimiliano Papaleo