-1

(this is for .Net Framework 4.7)

I'm trying to write up some extension methods to aid in creating dynamic where clauses for various entities. I started a few days ago, so there's likely a lot that I don't know and some that I probably misunderstood.

I managed to create one extension method already for filtering by 1 property which works as I expect it to (I did use reflection to get the property, couldn't get it working with an interface - well, without it executing the sql that is). I can't seem to be able to get this one working for a lambda expression though.

Note, that the solution must not trigger sql execution. Because I was able to write up some variants that "worK', but they will trigger sql execution.

The way I work with this is that once I have the code ready, I start debugging and have the "query" in the watch. And it looks like this (notice the sql code) watched-query-sql

Once I step over my FilterString method call, it either turns into a sql result, or I get an exception (with current code), which it shouldn't: exception

So here's my current code that throws the exception (currently not dealing with the "match" parameter, I am implementing an "equals" call. There will be others like, starts With, like, etc)

The exception is just one of those "type mismatch" having function cannot be passed as param to string Equals or what not.

    public static IQueryable<T> FilterString<T>(this IQueryable<T> query, Match match,
        string criteriaItem, Expression<Func<T, string>> getItemString)
        where T : class
    {
        if (string.IsNullOrEmpty(criteriaItem))
        {
            return query;
        }

        var param = Expression.Parameter(typeof(T), "r");
        var selector = Expression.Lambda<Func<T, string>>(getItemString, param);
        Expression<Func<string, bool>> prototype = item => item == criteriaItem;
        var predicate = Expression.Lambda<Func<T, bool>>(
            prototype.Body.ReplaceParameter(prototype.Parameters[0], selector.Body),
            selector.Parameters[0]);

        return query.Where(predicate);
    }

and the one that executes the sql instead of just generating it sql-executed

    public static IQueryable<T> FilterString<T>(this IQueryable<T> query, Match match,
        string criteriaItem, Expression<Func<T, string>> getItemString)
        where T : class
    {
        if (string.IsNullOrEmpty(criteriaItem))
        {
            return query;
        }

        var param = Expression.Parameter(typeof(T), "r");
        //var value = Expression.Constant(getItemString);
        var equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
        var item = Expression.Invoke(getItemString, param);
        var body = Expression.Call(Expression.Constant(criteriaItem),
            equals,
            item);

        return query.Where(Expression.Lambda<Func<T, bool>>(body, param));
    }

calling these is done like so

query = query.FilterString(match, criteria_value, (r) => r.SomeProperty.MaybeSomeOtherProp.SomeString);
query = query.FilterString(match, criteria_value, (r) => r.SomeProperty.Name);

This same extension method will be called on any number of different entities, with nay number of different properties and prop names. I guess I could make use of the reflection version I got working and passing in all the property names in some array of some sort, but that is just plain ugly.

So long story short, how can I get this working in the way I explained above, taht is: having the sql generated instead of executed?

Thank you,

Note, the "ReplaceParameter" extension method is the one from here: https://stackoverflow.com/a/39206392/630515

ciuly
  • 532
  • 5
  • 13
  • I didn't found any question mark. So whats the real question? Are you looking for a code review? – Jeroen van Langen Mar 22 '21 at 22:17
  • what's the purpose of the Match parameter? I don't see it being used anywhere? – Marco Mar 23 '21 at 00:18
  • @Marco I explained the reason why it's missing in my question. Just read it carefully. Thanks – ciuly Mar 23 '21 at 14:31
  • @JeroenvanLangen sorry, I raised this as an issue with "I can't seem to be able to get this one working for a lambda expression though.", but I'm happy to end it with a question so it's more clear. Thanks – ciuly Mar 23 '21 at 14:33

1 Answers1

1

So, you're trying to merge your prototype item => item == criteriaItem. With a passed in string property expression, like (r) => r.SomeProperty.Name to create (r) => r.SomeProperty.Name == criteriaItem.

    Expression<Func<string, bool>> prototype = item => item == criteriaItem;
    var predicate = Expression.Lambda<Func<T, bool>>(
        ReplacingExpressionVisitor.Replace(
            prototype.Parameters[0],
            getItemString.Body,
            prototype.Body),
        getItemString.Parameters[0]);

And I think you're trying to do it this way so that criteriaItem is bound to an sql parameter, rather than being inlined as a string constant. But your question was a little hard to follow.

Jeremy Lakeman
  • 9,515
  • 25
  • 29
  • Sorry for the unclear question. Hope by adding the question is more clear? Also, I didn't think EF Core would be different than our .net framework in this regards, but seeing ReplacingExpressionVisitor is only present in .net 5, I also put a note that this is for v4.7 In the meantime I'm trying to see if anyone has written a similar method I can use (or maybe you cna point me in the right direction for it?) Thanks – ciuly Mar 23 '21 at 14:48
  • I used the mentioned ReplaceParameter ext method instead of .net5's ReplacingExpressionVisitor and adapted your suggestion to my code and I got it working, thanks – ciuly Mar 23 '21 at 15:12