首先在实体框架代码中使用搜索字符串搜索 DateTime

Posted

技术标签:

【中文标题】首先在实体框架代码中使用搜索字符串搜索 DateTime【英文标题】:Searching a DateTime using a search string in Entity Framework code first 【发布时间】:2017-03-11 18:17:00 【问题描述】:

我有一个 MVC 项目,它为每个模型项向用户输出一个可为空的 DateTime (DateTime?) 作为字符串,如下所示(我的用户界面有分页,所以我只对少量记录进行此计算 -也就是说,这部分没问题):

foreach (DocumentEvent item in Model.items)
    @(item?.TimeUtc?.ToString() ?? "N/A")

我想添加搜索功能。我尝试过如下搜索,但这并不高效,因为AsEnumerable 实现了我的列表,而我现在身处 C# 世界,枚举每条记录:

using (var context = new ClientEventsContext())
    var items = context.Events.AsEnumerable().Where(x => 
        (x?.TimeUtc?.ToString() ?? "N/A").Contains(model.search)
    );

相反,我想利用我的 SQL Server 数据库。如何在没有AsEnumerable 的情况下为上述代码构建一个 SQL Server 友好的查询,这将产生与我当前逻辑相同的结果?

【问题讨论】:

只要去掉AsEnumerable,在IQueryable级别执行查询 @Fals 你不能,它会引发异常,因为实体框架无法将(x?.TimeUtc?.ToString() ?? "N/A").Contains(model.search) 转换为 SQL 查询。 通常,人们使用范围搜索来查询日期和时间,并将它们保留在它们的原生数据格式中。 Contains 字符串搜索将毫无用处。我可能会搜索'0''/' @MattJohnson 这正是我想要支持的,搜索0/,如果可能的话......差不多,搜索DateTime 可能包含的任何部分,如图所示给用户。你提出了一个很好的观点,也许方法是错误的,也许我应该添加一个 DB 列来存储DateTime 的字符串表示形式,并显示给用户,然后查询它。 @Alexandru 您使用的是哪个版本的实体框架? 【参考方案1】:

我找到了解决方案。这种语法似乎有效:

using (var context = new ClientEventsContext())
    var items = context.Events.Where(x => 
        x.TimeUtc.HasValue && x.TimeUtc.Value.ToString().Contains(model.search)
    );

【讨论】:

但您在该解决方案中使用 AsEnumerable。我认为在 EF 6.1 之后,您可以在查询中使用 ToString()。 @yosoy 对不起,当我从问题中复制并粘贴它时,我忘记将其删除,没有它就可以工作。编辑了答案。 即使这个答案目前也有一些烦人的问题。例如,如果我看到这个时间戳3/19/2012 7:22:00 AM,搜索适用于 20127:22,但对于 3/19/2012 失败并且对于 7:22:00 失败......它必须使用 SQL Server DateTime 格式或其他东西...所以,也许,我需要找到一种方法来编辑我的答案,以同样的方式格式化它,或者更好的方法。 现在我似乎无法弄清楚如何获得数据库的精确查询友好格式。 你知道搜索字符串的格式和文化吗?【参考方案2】:

使用SqlFunctions 库将任何内容转换为原始查询并在 SQL 级别完成工作的另一种解决方案,例如:

using (var context = new ClientEventsContext())
var items = context.Events.Where(x =>
    SqlFunctions.PatIndex(model.search, 
    SqlFunctions.DateName('your pattern here', x?.TimeUtc) ?? "N/A").Value > -1
);

【讨论】:

StringConvert 似乎只需要 doubledecimal 吗? @Alexandru 每个原语都有一个重载。 据我所知不适用于DateTime?。如果您不相信我,请自行查看:msdn.microsoft.com/en-us/library/… 有 Checksum 但我不知道如何使用它。您尝试调用的方法可能有问题。 @Alexandru 当然.. 你是对的,那么你应该使用 DateName 我的错误 @Alexandru 你是对的。您的解决方案更好,因为每个原始类型都支持ToString,而且该解决方案不绑定到 SqlServer。 StringConvert 主要用于有限的小数格式。【参考方案3】:

以下是构建和使用 LINQ to Entities 兼容转换的方法,将日期转换为 M/d/yyyy h:mm:ss tt 格式的字符串。我将使用自定义“标记”方法并使用ExpressionVisitor 绑定实现,而不是将那个怪物嵌入到查询中。这样,您可以根据需要进行试验和更改格式(甚至添加一些控制参数),而不会影响查询的可读性。

一、实现:

public static class EFExtensions

    public static string ToCustomDateFormat(this DateTime value)
    
        // Should never happen
        throw new InvalidOperationException();
    

    public static IQueryable<T> ApplyCustomDateFormat<T>(this IQueryable<T> source)
    
        var expression = new CustomDateFormatBinder().Visit(source.Expression);
        if (source.Expression == expression) return source;
        return source.Provider.CreateQuery<T>(expression);
    

    class CustomDateFormatBinder : ExpressionVisitor
    
        protected override Expression VisitMethodCall(MethodCallExpression node)
        
            if (node.Method.DeclaringType == typeof(EFExtensions) && node.Method.Name == "ToCustomDateFormat")
            
                var date = Visit(node.Arguments[0]);
                var year = DatePart(date, v => DbFunctions.Right("0000" + v.Year, 4));
                var month = DatePart(date, v => v.Month.ToString());
                var day = DatePart(date, v => v.Day.ToString());
                var hour = DatePart(date, v => (1 + (v.Hour + 11) % 12).ToString());
                var minute = DatePart(date, v => DbFunctions.Right("0" + v.Minute, 2));
                var second = DatePart(date, v => DbFunctions.Right("0" + v.Second, 2));
                var amPM = DatePart(date, v => v.Hour < 12 ? "AM" : "PM");
                var dateSeparator = Expression.Constant("/");
                var timeSeparator = Expression.Constant(":");
                var space = Expression.Constant(" ");
                var result = Expression.Call(
                    typeof(string).GetMethod("Concat", new Type[]  typeof(string[]) ),
                    Expression.NewArrayInit(typeof(string),
                        month, dateSeparator, day, dateSeparator, year, space,
                        hour, timeSeparator, minute, timeSeparator, second, space, amPM));
                return result;    
            
            return base.VisitMethodCall(node);
        

        Expression DatePart(Expression date, Expression<Func<DateTime, string>> part)
        
            var parameter = part.Parameters[0];
            parameterMap.Add(parameter, date);
            var body = Visit(part.Body);
            parameterMap.Remove(parameter);
            return body;
        

        Dictionary<ParameterExpression, Expression> parameterMap = new Dictionary<ParameterExpression, Expression>();

        protected override Expression VisitParameter(ParameterExpression node)
        
            Expression replacement;
            return parameterMap.TryGetValue(node, out replacement) ? replacement : node;
        
    

然后是用法:

var items = context.Events
    .Where(x => x.TimeUtc != null && 
        x.TimeUtc.Value.ToCustomDateFormat().Contains(model.search))
    .ApplyCustomDateFormat();

【讨论】:

这真是漂亮的代码。我希望我能给你更多的分数。 不客气,伙计 :) 你的话比一些虚拟积分更有价值!

以上是关于首先在实体框架代码中使用搜索字符串搜索 DateTime的主要内容,如果未能解决你的问题,请参考以下文章

如何使用实体框架在文本字段中搜索空字符串?

实体框架、代码优先和全文搜索

实体框架代码优先和搜索标准

在实体框架中搜索

如何在实体框架代码优先方法中使用表值函数?

改善使用实体框架时的搜索功能延迟