1

I am dynamically creating a Linq expression based on string arrays and I'm running into a problem. The way that the expression is created and parenthesized it is causing this to throw an object null reference on Id 3. It creates this expression and If it were parenthesized correctly it wouldn't evaluate the second half of the expression and wouldn't throw an error I assume. Anyone have a way of creating the expression so it doesn't end up parenthesized like this?

{x => ((True And x.Id.ToString().ToLower().Contains("John")) Or ((x.Name != null) And     x.Name.ToString().ToLower().Contains("John")))}

class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Test
{
    public void someMethod()
    {
        var x = new List<Person>(new Person[] { 
            new Person { Id = 1, Name = "Jerry" },
            new Person { Id = 2, Name = "Mary" },
            new Person { Id = 3, Name = null },
            new Person { Id = 4, Name = "John" },
            new Person { Id = 5, Name = "Amy" }
        });

        var columns = new List<string>(new string[] {
            "Name",
            "Id"
        });
        var searchTerm = "John";

        var searchColumns = columns.Select(a => new { ColName = a });


        var type = typeof(Person);

        ParameterExpression paramExpr = Expression.Parameter(type, "x");
        Expression body = null;

        var piList = new List<PropertyInfo>();

        foreach (var s in searchColumns)
            piList.Add(type.GetProperty(s.ColName));

        if (piList[0].PropertyType.IsPrimitive || piList[0].PropertyType.Equals(typeof(DateTime)))
            body = Expression.Constant(true);
        else
            body = Expression.NotEqual(Expression.Property(paramExpr, piList[0]), Expression.Constant(null, piList[0].PropertyType));

        body = Expression.And(body,
                    Expression.Call(
                            Expression.Call(
                                Expression.Call(
                                    Expression.Property(paramExpr, piList[0]),
                                    typeof(Convert).GetMethod("ToString", Type.EmptyTypes)
                                ),
                                typeof(string).GetMethod("ToLower", new Type[0])
                            ),
                            typeof(string).GetMethod("Contains"),
                            Expression.Constant(searchTerm.ToLower())
                        ));

        for (int i = 1; i < piList.Count; i++)
        {
            Expression body1 = null;

            if (piList[i].PropertyType.IsPrimitive || piList[i].PropertyType.Equals(typeof(DateTime)))
                body1 = Expression.Constant(true);
            else
                body1 = Expression.NotEqual(Expression.Property(paramExpr, piList[i]), Expression.Constant(null, piList[i].PropertyType));

            body =
                Expression.Or(body,
                    Expression.And(body1,
                        Expression.Call(
                            Expression.Call(
                                Expression.Call(
                                    Expression.Property(paramExpr, piList[i]),
                                    typeof(Convert).GetMethod("ToString", Type.EmptyTypes)
                                ),
                                typeof(string).GetMethod("ToLower", new Type[0])
                            ),
                            typeof(string).GetMethod("Contains"),
                            Expression.Constant(searchTerm.ToLower())
                        )
                    ));
        }

        var lambda = Expression.Lambda<Func<Person, bool>>(body, paramExpr);
    }
}
Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
Enigma Neo
  • 35
  • 5

1 Answers1

1

Why not just build one of these. They both avoid the issue with null values for Name.

(x.Name ?? "").IndexOf("John", StringComparison.CurrentCultureIgnoreCase) >= 0

or, if you really want equality, not contains it as a substring

string.Equals(x.Name, "John", StringComparison.CurrentCultureIgnoreCase)

BTW - x.Id will never contain "John" and neither will lowercase strings.

Also, you might want to consider using a PredicateBuilder instead of building the expression directly.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • So this is a dumbed down version. I'm actually working on Generic Types so I don't know what fields will be passed which is why I have to use reflection. Basically you can pass any list and it will search for your term. I'll look into PredicateBuidler. – Enigma Neo Sep 11 '13 at 14:14
  • @EnigmaNeo - if you end up going with your expression approach consider using `Convert.ChangeType` rather than `ToString` If it's LINQ/SQL or LINQ/EF to MSSQL you shouldn't need to do the casing either. If it's objects, consider defining a static equality comparison method that can tolerate null values or different object types. – tvanfosson Sep 11 '13 at 15:20
  • Thanks for the idea. I think this will solve my problem. It's linq to objects so I can probably just create a static function that checks for it. Should be pretty easy. – Enigma Neo Sep 11 '13 at 15:31