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 是一个枚举值
- 开始 // 服务开始日期
- 结束 // 服务结束日期
- 创建 // 订单创建日期
还有 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))
我正在开发一个酒店领域应用程序 (.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”
频道相同 "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))