The DateTimeExpressionCreator class provides developers with a versatile solution for creating DateTime comparison expressions for target DateTime properties. In this blog post, we'll delve into the intricacies of the class, dissecting its structure, methods, and underlying logic that makes it a valuable asset for handling dynamic DateTime filtering scenarios.

internal sealed class DateTimeExpressionCreator : IExpressionCreator
{
    public List<Expression<Func<T, bool>>> CreateExpressions<T>
        (string searchValue, Expression<Func<T, object>> keySelector)
    {
        object typedSearchValue = GetTypedSearchValue(searchValue);
        if (typedSearchValue == null)
        {
            return new List<Expression<Func<T, bool>>>();
        }

        List<MemberExpression> dateTimeMemberExpressions = keySelector.Body
            .ExtractDateTimeMemberExpressions();

        var dateOnlyExpressions = new List<Expression<Func<T, bool>>>();
        foreach (var dateTimeMemberExpression in dateTimeMemberExpressions)
        {
            List<string> properties = dateTimeMemberExpression.GetPropertyChain();

            AddTailProperty(properties, typedSearchValue);

            ParameterExpression parameterExpression = keySelector.Parameters.Single();

            Expression expression = parameterExpression;
            foreach (var property in properties)
            {
                expression = Expression.Property(expression, property);
            }

            try
            {
                ConstantExpression constantExpression = CreateConstantExpression(typedSearchValue);

                BinaryExpression equalityExpression = Expression.Equal
                    (expression, constantExpression);

                Expression<Func<T, bool>> dateOnlyExpression = Expression.Lambda<Func<T, bool>>
                    (equalityExpression, parameterExpression);

                dateOnlyExpressions.Add(dateOnlyExpression);
            }
            catch { }
        }

        return dateOnlyExpressions;
    }
    private object GetTypedSearchValue(string searchValue)
    {
        bool dateOnlyParsingResult = DateOnly.TryParse
            (searchValue, out DateOnly searchedDateOnlyValue);
        if (dateOnlyParsingResult)
        {
            return searchedDateOnlyValue;
        }

        bool timeOnlyParsingResult = TimeOnly.TryParse
            (searchValue, out TimeOnly searchedTimeOnlyValue);
        if (timeOnlyParsingResult)
        {
            return searchedTimeOnlyValue;
        }

        bool dateTimeParsingResult = DateTime.TryParse
            (searchValue, out DateTime searchedDateTimeValue);
        if (dateTimeParsingResult)
        {
            return searchedDateTimeValue;
        }

        return null!;
    }
    private void AddTailProperty(List<string> properties, object typedSearchValue)
    {
        string tailProperty = typedSearchValue switch
        {
            DateOnly => "Date",
            TimeOnly => "TimeOfDay",
            _ => string.Empty
        };

        if (!string.IsNullOrWhiteSpace(tailProperty))
        {
            properties.Add(tailProperty);
        }
    }
    private ConstantExpression CreateConstantExpression(object typedSearchValue)
    {
        return typedSearchValue switch
        {
            DateOnly => Expression.Constant(((DateOnly)typedSearchValue).ToDateTime(new TimeOnly())),
            TimeOnly => Expression.Constant(((TimeOnly)typedSearchValue).ToTimeSpan()),
            _ => Expression.Constant((DateTime)typedSearchValue)
        };
    }
}

The stages of the CreateExpressions method are as follows:

  1. Typed Search Value Retrieval: The method begins by obtaining a typed search value using the GetTypedSearchValue method, which parses the search value into DateOnly, TimeOnly, or DateTime.
  2. Parsing Check and Early Return: If the parsing fails and the typed search value is null, the method returns an empty list of expressions.
  3. Extracting DateTime Member Expressions: The class then extracts all DateTime member expressions from the provided keySelector using the ExtractDateTimeMemberExpressions extension method.
  4. Iterating Over DateTime Member Expressions: For each DateTime member expression, the method retrieves the associated property chain.
  5. Tail Property Addition: It adds a tail property to the property chain based on the type of the DateTime value using the AddTailProperty method. The tail string here is the name of the property ("Date" or "TimeOfDay") which is used for DateOnly or TimeOnly comparison.
  6. Building Property Access Chain: The method constructs the property access chain within the expression, preparing it for further manipulation.
  7. Type-Specific Constant Expression Creation: It creates a type-specific constant expression for the typed search value using the CreateConstantExpression method.
  8. Equality Expression Creation: The method builds an equality expression between the property access chain and the constant expression.
  9. Creating Expression: The final step involves creating a Lambda expression encapsulating the equality expression, resulting in an Expression<Func<T, bool>>.
  10. Aggregating Expressions: All generated expressions are added to the list, which is then returned as the method output.

Helper Methods: The DateTimeExpressionCreator class includes three additional private methods - GetTypedSearchValue, AddTailProperty, and CreateConstantExpression - to handle type-specific parsing, property tail addition, and constant expression creation, respectively.