Entity Framework 核心中的动态查询执行

Dynamic query execution in Entity Framework Core

我正在开发一个酒店领域应用程序 (.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 是一个枚举值

  1. 开始 // 服务开始日期
  2. 结束 // 服务结束日期
  3. 创建 // 订单创建日期

还有 OrdersStatus(枚举),其值为 All 、 Confirmed 、 Canceled 、 PaymnetFailed 等

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

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

频道相同 "Web" 或 "Web, Mobile"

目前我使用的ef core expresssssion如下

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 
                     );

这里的 Guests 和 Services 是在 Orders Model

中设置为导航 属性 的另外两个表

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

此外,如果未提供任何过滤器的值,则排除任何过滤器的最佳做法是什么。

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

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

任何人都可以提出一些好的 material 或任何与此相关的代码,以便我可以对其进行优化。

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

编辑1:Client评价设为关闭

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

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

在 EF Core 出现之前,人们希望有条件地链接多个 Where 调用或使用某些谓词构建器实用程序来有条件地构建 Where 仅具有必要条件的谓词。

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

你在大多数情况下都是这样做的,但前 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))