C#中使用泛型DateTime.ToString()函数自定义日期格式

3

当使用时:

DateTime.ToString().Contains("2016")

Entity Framework 生成:

CAST(DateValue AS nvarchar(max)) LIKE '%2016%'

这里使用的是默认的日期格式"mon dd yyyy hh:miAM (或 PM)"

我想要使用"yyyy-mm-dd hh:mi:ss (24小时制)",可以通过以下方式获得:

CONVERT(VARCHAR(max), DateValue, 20) LIKE '%2016%'

我需要帮助将这种格式应用到现有的通用方法中。

static Expression<Func<T, TResult>> Expr<T, TResult>(Expression<Func<T, TResult>> source) { return source; }
static MethodInfo GetMethod(this LambdaExpression source) { return ((MethodCallExpression)source.Body).Method; }
static readonly MethodInfo Object_ToString = Expr((object x) => x.ToString()).GetMethod();
static readonly MethodInfo String_Contains = Expr((string x) => x.Contains("y")).GetMethod();

public static IQueryable<T> Filter<T>(this IQueryable<T> query, List<SearchFilterDto> filters)
 where T : BaseEntity
{
    if (filters != null && filters.Count > 0 && !filters.Any(f => string.IsNullOrEmpty(f.Filter)))
    {
        var item = Expression.Parameter(query.ElementType, "item");
        var body = filters.Select(f =>
        {
            var value = f.Column.Split('.').Aggregate((Expression)item, Expression.PropertyOrField);
            if (value.Type != typeof(string))
            {
                value = Expression.Call(value, Object_ToString);
            }

            return (Expression)Expression.Call(value, String_Contains, Expression.Constant(f.Filter));
        })
        .Where(r => r != null)
        .Aggregate(Expression.AndAlso);

        var predicate = Expression.Lambda(body, item);
        MethodInfo whereCall = (typeof(Queryable).GetMethods().First(mi => mi.Name == "Where" && mi.GetParameters().Length == 2).MakeGenericMethod(query.ElementType));
        MethodCallExpression call = Expression.Call(whereCall, new Expression[] { query.Expression, predicate });
        query = query.Provider.CreateQuery<T>(call);
    }
    return query;
}

请注意,这只是一个例子,它不总是"2016",也不总是一年。用户可以输入时间或"01"来检索所有记录,这些记录可能是在月份的第一天、一月或2001年。这是一个非常灵活的过滤器。
我知道很多人可能不喜欢这种情况,但我真的在寻找解决方案,而不是被告知"不要这样做"。
解决方案还需要考虑到LINQ to Entities,因此我不能简单地使用.ToString("MMM d yyyy H:mm tt"),因为这会导致:"LINQ to Entities不认识'System.String ToString(System.String)'方法,这个方法无法转换成存储表达式。"
代码可以使用默认的日期格式工作。我提问的原因是要通过操作Entity Framework中的查询来改变SQL级别的日期格式。

我认为在日期中使用LIKE并不是一个好主意。你可以通过不将日期转换为字符串来获得更有效的查询。实际上,你想要实现什么,也就是说,在实践中你会用什么来代替“xyz”? - Matti Virkkunen
@MattiVirkkunen,我已经用一个更好的例子 2016 替换了 xyz。这个文本是来自于一个开放文本框。如果你有更好的处理日期的方式,我也会支持的!它只需要能够灵活准确地处理任何字符串/过滤器中包含的日期。 - Sir Crusher
你不应该像那样混合代码和数据。你可能应该将文本字段解析为整数(以获取年份)。SQL 有 DATEPART 函数,EF 可能会将 DateTime.Year == value 翻译成 DATEPART。 - Oleh Nechytailo
@OlehNechytailo,我已经在问题的末尾添加了更多文本。 - Sir Crusher
2个回答

1

如果您想确定日期是否在您的输入值所在的年份内,为什么不这样做:

DateTime.Year == 2016 //或者您的变量

也许我没有完全理解您的需求。


你如何知道传递的是月份、日期还是年份? - Nick
你不需要这样做,也不必这样做。代码可以使用默认的日期格式。我提出这个问题的原因是想通过在Entity Framework中操作查询来改变SQL级别的日期格式。 - Sir Crusher
你是正确的,它应该匹配 - 因此用户必须更具体地指定值 - 以限制过滤结果。 - Sir Crusher
所以基本上你允许用户提供一个模式来匹配日期? - Nick
是的,在一个空文本框的形式下。如果他们输入了一些愚蠢或不具体的内容,他们将得不到好的结果。 - Sir Crusher
显示剩余6条评论

1
我发现唯一的方法是手动构建表达式来实现所需的结果。
Expression<Func<DateTime, string>> Date_ToString = date =>
    DbFunctions.Right("000" + date.Year.ToString(), 4) + "-" +
    DbFunctions.Right("0" + date.Month.ToString(), 2) + "-" +
    DbFunctions.Right("0" + date.Day.ToString(), 2) + " " +
    DbFunctions.Right("0" + date.Hour.ToString(), 2) + ":" +
    DbFunctions.Right("0" + date.Minute.ToString(), 2) + ":" +
    DbFunctions.Right("0" + date.Second.ToString(), 2);

我知道这很丑陋。坦率地说,您不想看到上述表达式生成的 EF SQL - 与所需的 CONVERT(...) 相比,它是一个巨大的怪物。但至少它能工作。
以下是代码。可以使用 System.Linq.Expressions 构建上述表达式,但我懒得这样做,而是使用了一个简单的参数替换器。
修改后的部分:
if (value.Type != typeof(string))
{
    if (value.Type == typeof(DateTime))
        value = value.ToDateString();
    else if (value.Type == typeof(DateTime?))
        value = Expression.Condition(
            Expression.NotEqual(value, Expression.Constant(null, typeof(DateTime?))),
            Expression.Property(value, "Value").ToDateString(),
            Expression.Constant(""));
    else
        value = Expression.Call(value, Object_ToString);
}

和使用的帮助程序:

static readonly Expression<Func<DateTime, string>> Date_ToString = date =>
    DbFunctions.Right("000" + date.Year.ToString(), 4) + "-" +
    DbFunctions.Right("0" + date.Month.ToString(), 2) + "-" +
    DbFunctions.Right("0" + date.Day.ToString(), 2) + " " +
    DbFunctions.Right("0" + date.Hour.ToString(), 2) + ":" +
    DbFunctions.Right("0" + date.Minute.ToString(), 2) + ":" +
    DbFunctions.Right("0" + date.Second.ToString(), 2);

static Expression ToDateString(this Expression source)
{
    return Date_ToString.ReplaceParameter(source);
}

static Expression ReplaceParameter(this LambdaExpression expression, Expression target)
{
    return new ParameterReplacer { Source = expression.Parameters[0], Target = target }.Visit(expression.Body);
}

class ParameterReplacer : ExpressionVisitor
{
    public ParameterExpression Source;
    public Expression Target;
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node == Source ? Target : base.VisitParameter(node);
    }
}

绝对完美!我在构建一个巨型的datepart-concat混合物时,出现了SQL“嵌套到第10层”的异常。 我需要仔细阅读这段代码,我会从中学到很多东西。 感谢您花费时间和精力,我非常感激! - Sir Crusher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接