3

I want to make a dynamic check for a null value. I want to make a where clause which will compare only the date part of the date field.

It will work for non nullable date fields, but for nullable date fields we need to check for value as using .Date on null data will throw an error

let us say

p => (p.Date.Value == null ? null : p.Date.Value.Date) == SelectedDate.Date

or

 p => ( p.Date.Value == null ? p.Date.Value : p.Date.Value.Date) == SelectedDate.Date

or

p => (p.Date.Value == null ? p.Date : p.Date.Value.Date) == SelectedDate.Date

basically a null checking ternary operator which selects only the date part of

I already tried

ConstantExpression argument = Expression.Constant(MyDateField, typeof(DateTime));
ParameterExpression parameter = Expression.Parameter(typeof(T), "p");
string field = "Date";
BinaryExpression condition = Expression.Equal(Expression.Property(parameter, field), Expression.Constant(null, typeof(DateTime?)));
ConditionalExpression ternary = Expression.Condition(condition, property, Expression.Property(property, "Date"));
Expression equalExp = Expression.Equal(ternary, argument);
lambda = Expression.Lambda<Func<T, bool>>(equalExp, parameter);

Which gives me

p => (IIF((p.EventDate == null), p.EventDate.Value, p.EventDate.Value.Date) == 21-Jun-18 12:00:00 AM)

but this is not working. Issue I'm facing is

If I use p.Date.Value in the BinaryExpression then it doesnot allow as .Value makes it DateTime and null is only available in DateTime?

IIF condition is generated and not ?: ternary operator

Any and all help is appreciated.

Devanshi Parikh
  • 265
  • 4
  • 12

4 Answers4

3

The DateTime? and DateTime are different types. While the C# compiler does some implicit conversions sometimes (for example when you compare them with ==), with Lambda Expressions you have to make explicit casts. And to get the value of a DateTime? you have to use the .Value property.

public static Expression<Func<T, bool>> MakeExpression<T>(DateTime myDateField)
{
    ConstantExpression argument = Expression.Constant(myDateField, typeof(DateTime));
    ParameterExpression parameter = Expression.Parameter(typeof(T), "p");

    string propertyName = "Date";
    Expression property = Expression.Property(parameter, propertyName);

    BinaryExpression condition = Expression.Equal(property, Expression.Constant(null, typeof(DateTime?)));
    Expression propertyValue = Expression.Property(property, nameof(Nullable<DateTime>.Value));
    Expression propertyValueDate = Expression.Property(propertyValue, nameof(DateTime.Date));
    ConditionalExpression ternary = Expression.Condition(condition, Expression.Constant(null, typeof(DateTime?)), Expression.Convert(propertyValueDate, typeof(DateTime?)));
    Expression argumentDate = Expression.Property(argument, nameof(DateTime.Date));
    Expression equalExp = Expression.Equal(ternary, Expression.Convert(argumentDate, typeof(DateTime?)));
    var lambda = Expression.Lambda<Func<T, bool>>(equalExp, parameter);
    return lambda;
}

Note that Nullable<> defines a HasValue property, instead of comparing the value with null... So you could:

public static Expression<Func<T, bool>> MakeExpression<T>(DateTime myDateField)
{
    ConstantExpression argument = Expression.Constant(myDateField, typeof(DateTime));
    ParameterExpression parameter = Expression.Parameter(typeof(T), "p");

    string propertyName = "Date";
    Expression property = Expression.Property(parameter, propertyName);

    Expression propertyHasvalue = Expression.Property(property, nameof(Nullable<DateTime>.HasValue));
    Expression propertyValue = Expression.Property(property, nameof(Nullable<DateTime>.Value));
    Expression propertyValueDate = Expression.Property(propertyValue, nameof(DateTime.Date));
    ConditionalExpression ternary = Expression.Condition(Expression.Not(propertyHasvalue), Expression.Constant(null, typeof(DateTime?)), Expression.Convert(propertyValueDate, typeof(DateTime?)));
    Expression argumentDate = Expression.Property(argument, nameof(DateTime.Date));
    Expression equalExp = Expression.Equal(ternary, Expression.Convert(argumentDate, typeof(DateTime?)));
    var lambda = Expression.Lambda<Func<T, bool>>(equalExp, parameter);
    return lambda;
}
xanatos
  • 109,618
  • 12
  • 197
  • 280
  • the expression being made is correct, but I am getting the error "The specified type member 'Date' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported." the field on which I'm trying to filter is nullable. – Devanshi Parikh Jun 21 '18 at 07:06
  • 1
    @DevanshiParikh Entity Framework probably doesn't support the `DateTime.Date`. See https://stackoverflow.com/a/9642274/613130 for possible solutions. `DbFunctions` for EF6.0, `EF.Functions` for EF Core – xanatos Jun 21 '18 at 07:07
3

Let say we have two expressions left and right, where the right type is DateTime, and we want to compare them for equality.

When the left type is DateTime, the comparison is simply

left == right

and when the left type is DateTime?, then

(left == (DateTime?)null ? (DateTime?)null : (DateTime?)left.Value.Date) == (DateTime?)right

I specifically added the required casts. C# compiler does some of them implicitly (like (DateTime?)null), but the important is that the ternary operator result type should be DateTime?, hence both ternary operator operands type and equality operator operands type must be DateTime? as well.

With that being said, let translate the aforementioned rules to code:

static Expression<Func<T, bool>> DateEquals<T>(string memberName, DateTime value)
{
    var parameter = Expression.Parameter(typeof(T), "p");
    Expression left = Expression.PropertyOrField(parameter, memberName);
    Expression right = Expression.Constant(value.Date);
    if (left.Type == typeof(DateTime?))
    {
        var leftValue = Expression.Property(left, "Value");
        var nullValue = Expression.Constant(null, typeof(DateTime?));
        left = Expression.Condition(
            Expression.Equal(left, nullValue),
            nullValue,
            Expression.Convert(Expression.Property(leftValue, "Date"), typeof(DateTime?))
        );
        right = Expression.Convert(right, typeof(DateTime?));
    }
    var condition = Expression.Equal(left, right);
    return Expression.Lambda<Func<T, bool>>(condition, parameter);
}

(Don't worry that you see IIF in the debug display. The Conditional expression shown as IIF is indeed the expression equivalent of the C# ? : operator)

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
0

I suppose your p.Date is DateTime? (or Nullable<DateTime>)

p => p.Date?.Date == SelectedDate.Date
vasily.sib
  • 3,871
  • 2
  • 23
  • 26
0

What ended up working is

public static Expression<Func<T, bool>> MakeExpression<T>(DateTime myDateField, string fieldName)
    {
        var parameter = Expression.Parameter(typeof(T), "p");
        var property = Expression.Property(parameter, fieldName);
        var fieldType = property.Type;
        Expression<Func<T, bool>> lambda = null;

        if (fieldType == typeof(DateTime?))
        {
            var truncateTimeMethod = typeof(DbFunctions).GetMethod("TruncateTime", new[] { fieldType });
            if (truncateTimeMethod != null)
            {
                var propertyHasvalue = Expression.Property(property, nameof(Nullable<DateTime>.HasValue));
                var truncateTimeMethodCall = Expression.Call(truncateTimeMethod, property);
                var ternary = Expression.Condition(Expression.Not(propertyHasvalue), property, truncateTimeMethodCall);
                var argument = Expression.Constant(myDateField.Date, typeof(DateTime?));
                var equalExp = Expression.Equal(ternary, argument);

                lambda = Expression.Lambda<Func<T, bool>>(equalExp, parameter);
            }
        }
        return lambda;
    }

Thanks to @xanatos

For the .Date functionality

var truncateTimeMethod = typeof(DbFunctions).GetMethod("TruncateTime", new[] { fieldType });
var truncateTimeMethodCall = Expression.Call(truncateTimeMethod, property);

For Ternary Operation

var ternary = Expression.Condition(Expression.Not(propertyHasvalue), property, truncateTimeMethodCall);
Devanshi Parikh
  • 265
  • 4
  • 12