Entity Framework Core 中的动态查询执行

Posted

技术标签:

【中文标题】Entity Framework Core 中的动态查询执行【英文标题】:Dynamic query execution in Entity Framework Core 【发布时间】:2020-09-02 03:17:45 【问题描述】:

我正在开发一个酒店领域应用程序 (.Net Core 2.2),我正在其中开发一个报告模块。在仪表板中,有几个过滤器可用于从数据库中获取记录。

下面是我用来包含过滤器的 DTO

public class SearchDto

    public DateTime DateForm get;set;
    public DateTime DateTo get;set;
    public DateSearchType SearchType get;set;
    public string RegionId get;set;
    public OrdersStatus status get;set;
    public string PaymentModeTypes get;set;
    public string channel get;set;

这里 DateSearchType 是一个有值的枚举

    开始 // 服务开始日期 结束 // 服务结束日期 创建 // 订单创建日期

还有 OrdersStatus(一个枚举),其值包括 All 、 Confirmed 、 Canceled 、 PaymnetFailed 等

PaymentModeTypes 可以是单个字符串或逗号分隔的字符串,例如:“NetBanking、CreditCard、DebitCard、Cash”

RegionId 也是单个字符串或逗号分隔的字符串,如 "101, 102, 102"

“网络”或“网络、移动”频道相同

我目前使用的ef核心表达如下

var v = Database.Orders.List(
              x => ((SearchType == DateSearchType.Start) ? x.Services.Any(y => (y.MinStartTime >= DateForm.Date && y.MinStartTime <= DateTo.Date)) || DateForm.Date.Equals(DateTime.MinValue) : true)
              && ((SearchType == DateSearchType.End) ? x.Services.Any(y => y.MaxEndTime >= DateForm.Date && y.MaxEndTime <= DateTo.Date) : true)
              && ((SearchType == DateSearchType.Creation) ? x.BookingDate.Date >= DateForm.Date && x.BookingDate.Date <= DateTo.Date : true)
              && (RegionId.Length == 0 || RegionId.Contains(x.RegionId))
              && (status == OrdersStatus.All || x.Status == status)
              && (string.IsNullOrEmpty(PaymentModeTypes) || PaymentTypes.Contains(x.PaymentType))
              && (string.IsNullOrEmpty(channel) || channels.Contains(x.ChannelName)), //Where
                     x => x.Guests, 
                     x => x.Services 
                     );

这里的 Guest 和 Services 是在 Orders 模型中设置为导航属性的另外两个表

这个表达式工作正常,但执行时间太长,有什么好的方法来优化它或重写这段代码的正确方法吗?

如果没有提供任何过滤器的值,排除过滤器的最佳做法是什么。

很少有过滤器不是强制性的,可以提供也可以不提供,因此查询执行必须是动态的。未提供过滤器值时的当前实现

 && (string.IsNullOrEmpty(PaymentModeTypes) || PaymentTypes.Contains(x.PaymentType))

任何人都可以建议一些好的材料或任何关于此的代码,以便我可以优化它。

请忽略错字,因为我不习惯使用深色主题

编辑 1:客户端评估设置为关闭

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    
        optionsBuilder
            .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
    

【问题讨论】:

过滤器是转换成 SQL 还是在内存中求值? 它在 mysql 数据库上运行,而不是在内存集合中 没错,但您使用的是支持所谓的“客户端评估”的 EF Core 2.x。因此,为了确保过滤发生在数据库中,请关闭客户端评估,如下所示docs.microsoft.com/en-us/ef/core/querying/…。这是首先要检查的。请先确认一下,然后我们可以考虑查询是否可以优化。 它没有在我的数据库上下文中配置,所以我猜默认情况下它是打开的,将它设置为关闭参见编辑 1: 你好@IvanStoev 现在有什么建议吗? 【参考方案1】:

生成的 SQL 效率低下,可能会导致性能问题。

在 EF Core 之前,预计会有条件地链接多个 Where 调用或使用一些谓词构建器实用程序来有条件地构建仅具有必要条件的 Where 谓词。

在 EF Core 中通常不需要这样做,因为它会尝试自动消除此类情况。它对逻辑表达式 (|| , &amp;&amp;) 执行此操作,但对条件表达式 (? :) 不执行此操作。所以解决办法就是把后面的替换成等价的逻辑表达式。

你在大多数情况下都是这样做的,但不是前 3 个。所以替换

x => ((SearchType == DateSearchType.Start) ? x.Services.Any(y => (y.MinStartTime >= DateForm.Date && y.MinStartTime <= DateTo.Date)) || DateForm.Date.Equals(DateTime.MinValue) : true)
    && ((SearchType == DateSearchType.End) ? x.Services.Any(y => y.MaxEndTime >= DateForm.Date && y.MaxEndTime <= DateTo.Date) : true)
    && ((SearchType == DateSearchType.Creation) ? x.BookingDate.Date >= DateForm.Date && x.BookingDate.Date <= DateTo.Date : true)

x => (SearchType != DateSearchType.Start || x.Services.Any(y => y.MinStartTime >= DateForm.Date && y.MinStartTime <= DateTo.Date) || DateForm.Date.Equals(DateTime.MinValue))
    && (SearchType != DateSearchType.End || x.Services.Any(y => y.MaxEndTime >= DateForm.Date && y.MaxEndTime <= DateTo.Date))
    && (SearchType != DateSearchType.Creation || x.BookingDate.Date >= DateForm.Date && x.BookingDate.Date <= DateTo.Date)

看看是否有帮助。生成的 SQL 肯定是最优的。


注意到您对逗号分隔的字符串和Contains 过滤器使用了不同的变量名称。所以我假设你有这样的东西

public IEnumerable<string> PaymentTypes => (PaymentModeTypes ?? "").Split(", ");
public IEnumerable<string> channels => (channel ?? "").Split(", ");

这很好,必要时会生成 SQL IN (...) 条件。

您可以考虑对地区做同样的事情,例如添加

public IEnumerable<string> RegionIds => (RegionId ?? "").Split(", ");

替换

&& (RegionId.Length == 0 || RegionId.Contains(x.RegionId))

&& (string.IsNullOrEmpty(RegionId) || RegionIds.Contains(x.RegionId))

【讨论】:

以上是关于Entity Framework Core 中的动态查询执行的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework Core 2 中的 ConnectionString 生成器

根据 ASP.NET Core 和 Entity Framework Core 中的条件禁用 [必需] 属性

Entity Framework Core 中的动态数据模型

Entity Framework Core 中的动态查询执行

无法更新 Entity Framework Core 中的标识列

ASP.NET Core 和 Entity Framework Core:Linq 中的左(外)连接