1

I have an expression that returns a DateTime column in a query.

Expression<Func<T, DateTime>> GetDateExpression;

GetDateExpression = td => td.DateShipped;

But now I want to use that expression to form a filter in a SQL query.

query = query.Where(td => GetDateExpression(td) >= startDate);

But that won't compile.

error CS1955: Non-invocable member 'DateFilter.GetDateExpression' cannot be used like a method.

Can anyone show me how to construct the where clause above using GetDateExpression?

E. Shcherbo
  • 1,128
  • 8
  • 15
Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • Check this my answer: https://stackoverflow.com/questions/66378438/can-i-reuse-code-for-selecting-a-custom-dto-object-for-a-child-property-with-ef/66386142#66386142 – Svyatoslav Danyliv Aug 03 '21 at 21:41
  • @SvyatoslavDanyliv: Are you saying to use a third-party library for this? I'm not convinced at all that that's necessary. In fact, I'm pretty sure it's not. Although I don't know if I need to manually build the expression tree to make this work. – Jonathan Wood Aug 03 '21 at 21:43
  • You have to write a method which constructs a `Lambda` expression with body `Expression.GreaterThanOrEqual(yourGetDateExpression, valueOfStartDateExpression)` and you also should replace the parameter of `yourGetDateExpression`. Take a look at these answers: https://stackoverflow.com/questions/38507906/how-can-i-include-one-expression-in-another-expression and https://stackoverflow.com/questions/5430996/replacing-the-parameter-name-in-the-body-of-an-expression and https://stackoverflow.com/questions/6736505/how-to-combine-two-lambdas. – E. Shcherbo Aug 03 '21 at 21:49
  • @E.Shcherbo: Replace `yourGetDataExpression` with what? How can I incorporate the expression it contains? – Jonathan Wood Aug 03 '21 at 21:51
  • Not the expression itself, but the parameter of it. See the thing, the `td` in your `GetDateExpression` and `td` in `Where` are different things, therefore you need to replace `td` of `GetDateExpression` with `td` of `Where`. As far as I know the replacement can be done with a kind of `expression visitor` which you can find following by the links I sent – E. Shcherbo Aug 03 '21 at 21:52
  • @E.Shcherbo: Without the argument, it has no entity reference to get the column. – Jonathan Wood Aug 03 '21 at 21:54
  • @JonathanWood, well probably our experience is different ;) EF Core itself won't parse such constructions. For example with LINQKit you can call `GetDateExpression.Invoke(td)` and it will work inside `Where` condition. Don't afraid third party libraries they are very thin and plain for such easy task. – Svyatoslav Danyliv Aug 03 '21 at 21:56
  • @SvyatoslavDanyliv: Well, I don't know about you but I've been programming since 1987 and I have some pretty strong opinions about how I like to approach things. This can be done in plain C#, so that would be my preference. – Jonathan Wood Aug 04 '21 at 03:21
  • @JonathanWood, our experience is different, not sure that you have developed LINQ translator but i did. Actually your question is closed and pointed to implementation that already done in third party libraries. – Svyatoslav Danyliv Aug 04 '21 at 05:34
  • @SvyatoslavDanyliv: No, I haven't developed a LINQ translator. Have you developed a custom language and interpreter written from scratch, or a full-fledged HTML parser? My point is I've been doing this a while. I'm not going to change my mind because someone tells me not to be *afraid* of third party libraries. – Jonathan Wood Aug 04 '21 at 16:30
  • @JonathanWood, question is about LINQ. Just noticing that I have big experience in this area. I have also contributed into LINQKit - it is well tested and really small library. Anyway you can play with that by yourself. – Svyatoslav Danyliv Aug 04 '21 at 19:45

1 Answers1

2

You can write a method to construct a new GreaterThanOrEqual expression from two operands

The idea behind this is the following. You want to construct a new expression which looks like this:

td => td.DateShipped >= startDate.Value

using the body of your existing expression td => td.DateShipped, but important thing here is that td in the result expression and td in your GetDateExpression are different things, so if you just will write the greaterThanOrEqual expression without replacing you will get something like this:

td1 => td2.DateShipped >= startDate.Value

Therefore you need to replace td2 with td1 so that the expression looks like I wrote in the beginning. Therefore all the replacer does is replace each ParameterExpression it finds in the expression tree with our filterParam.

You can take a look at the following answers to read about this a bit more:

    Expression<Func<T, bool>> GreaterThanOrEqual(Expression<Func<T, DateTime?>> operand1, Expression<Func<T, DateTime?>> operand2)
    {
        var filterParam = Expression.Parameter(typeof(T));
        var greaterThanOrEqual = Expression.GreaterThanOrEqual(operand1.Body, operand2.Body);
        greaterThanOrEqual = (BinaryExpression) new ParameterReplacer(filterParam).Visit(greaterThanOrEqual);
        
        return Expression.Lambda<Func<T, bool>>(greaterThanOrEqual, filterParam);
    }
                                                                      
    internal class ParameterReplacer : ExpressionVisitor {
        private readonly ParameterExpression _parameter;

        protected override Expression VisitParameter(ParameterExpression node) {
            return base.VisitParameter(_parameter);
        }

        internal ParameterReplacer(ParameterExpression parameter) {
            _parameter = parameter;
        }
    }

Then use it like

    Expression<Func<T, DateTime?>> getDateExpression = m => m.Date;
    Expression<Func<MyClass, bool>> combined = GreaterThanOrEqual(getDateExpression, td => startDate.Value);
    
    query = query.Where(combined);
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
E. Shcherbo
  • 1,128
  • 8
  • 15
  • Thanks. Trying to work through this now. Can I ask what `ParameterReplacer` is? – Jonathan Wood Aug 03 '21 at 22:15
  • I updated my answer a bit with some info about this and with some additional links if it's not enough and more insight is needed. – E. Shcherbo Aug 03 '21 at 22:25
  • @E.Shcherbo Just FYI since EF Core 3.0 there is [`ReplacingExpressionVisitor`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.query.replacingexpressionvisitor?view=efcore-5.0) which can be used to perform the replacing task. – Guru Stron Aug 03 '21 at 22:48
  • @GuruStron yeah thank you very much, this is useful to know – E. Shcherbo Aug 03 '21 at 22:52
  • @E.Shcherbo also see my edit - no need to use html in your answer =))) – Guru Stron Aug 03 '21 at 22:53
  • @GuruStron thank you for fixing this :) I used html to fix the format of the source code after I added list items for the links. See now how you did this elegantly with ``` – E. Shcherbo Aug 03 '21 at 22:55
  • 1
    @E.Shcherbo also links can be added easily with CTRL+L shortcut. – Guru Stron Aug 03 '21 at 22:57