如何将整体查询分解为更模块化和更易于管理的组件?

How can I break-down my monolithic queries to more modular and manageable components?

背景

我正在开发 .Net Core - C# 门户后端,允许用户对数据执行筛选和查询。这采用后端端点的形式,通过查询参数接收选定的过滤器列表 and/or 查询,并根据这些参数返回相关数据。调用数据库以拉取相关数据是在一个完全无法理解的大量不可读的 where 子句中完成的。我对这个领域和这里使用的技术还很陌生。因此,我希望更好地理解可以做些什么来将其分解为更易于管理的东西。

Details/Examples

示例查询:

List<OrderEntity> entities = context.Order.Where(o => 
                    // List built in another query that takes place first
                    ((!regionFilter.Any() && !districtFilter.Any()) || locationFiltersToLocationIdList.Contains(o.locationId)) &
                    // Many more statements...
                    (!orderorderStatusList.Any() || (orderStatusList.Contains("Paid") && o.Status == "Paid") 
                                                 || (orderStatusList.Contains("Late") && o.Status != "Paid" &&
                                                     (o.OrderPaymentDueDate != null && DateTime.Compare((DateTime)o.OrderPaymentDueDate, DateTime.Now) < 0) || 
                                                     (o.OrderPaymentDueDate == null && o.OrderDate != null && o.PaymentTerms != null && 
                                                      DateTime.Compare(o.OrderDate.Value.AddDays(Convert.ToInt32(o.paymentInterval)), DateTime.Now) < 0))) &&
                    
                    // Above query segment handels status interpretation,
                    // because the status value on a order cannot be fully trusted.
                    
                    // This kind of on the fly 'field value interpretation' is commonly required
                    (dueToDate == null || (o.OrderPaymentDueDate != null && (o.OrderPaymentDueDate != null && DateTime.Compare((DateTime)o.OrderPaymentDueDate, DateTime.Now) <= 0) || 
                                           (o.OrderPaymentDueDate == null && 
                                            o.OrderDate != null && 
                                            o.PaymentTerms != null 
                                            && DateTime.Compare(o.OrderDate.Value.AddDays(Convert.ToInt32(o.paymentInterval)), DateTime.Now) >= 0)))
                    
                    // In both segments we handle case where the query doesnt filter OrderPaymentDueDate,
                    // When it does, and when it should but OrderPaymentDueDate is null or empty.
                ).OrderBy(p => o.OrderDate).ToList();

尽管这与我实际处理的查询的规模相去甚远,但它有望说明问题。我不禁觉得必须有一种更加模块化和更简洁的方式来动态构建此查询。研究突出了 deferred execution、潜在的软件包和似乎永远不够深入的无限文档等主题。

问题:

如能提供有关 what/how 此类通常会处理的大型复杂查询的信息,我们将不胜感激。

具体化

在您调用方法来 具体化 查询之前,您可以 构建 它。 例如

var query = context.Order.Where(o => o.Status == "Paid");

if (myCondition)
{
    query = query.Where(o => o.OrderPaymentDueDate != null)
}

// Now materialize the query, result contain the data
var result = query.ToList();

拆分查询

对于大型(慢速)查询或不受支持的代码,您可以考虑单独执行然后在客户端合并

var query1 = context.Order.Where(o=> o.Field == "Foo").ToList(); // VERY Slow query1 materialized

var query2 = context.Customers.Where(o=> o.Field == "Bar").ToList(); // VERY Slow query2 materielized

var result = from q1 in query1
             from q2 in query2
             join q1.Id equals q2.Id
             select new 
             {
                Q1Id = q1.Id, 
                Q2Is = q2.Id,
                [..]

             }

另一个例子是数据操作(警告这适用于客户端代码优于生成的 SQL 代码的一小部分数据)

var execQuery = context.Order.Where(o => o.MyDate > DateTime.Now).ToList(); //Pre-filter on SQL

var result = execQuery.Where(o=> o.MyDate.Month == 4); // Now I can use ALL Linq operations

观看次数3.x

你可以考虑编写 SQL 视图并将它们连接到 EF。

Source 3.x

Source 5.x

// Define model
public class MyView
{
    public int Id { get; set; }
    public string Name { get; set; }
}


// Define DbSet
public DbSet<MyView> MyView { get; set; }


// Configure as view, different from EF 3 to 5
modelBuilder
    .Entity<MyView>()
    .ToView(nameof(MyView))
    .HasKey(t => t.Id);

// Call on code
var view = context
        .MyView
        .ToList();

原始查询

如果生成的查询未优化,您也可以编写自己的查询。

您可以使用 FromSqlRaw 扩展方法开始基于原始 SQL 查询的 LINQ 查询。 FromSqlRaw 只能用于查询根,即直接用于 DbSet<>

Source

var blogs = context.Blogs
    .FromSqlRaw("SELECT * FROM dbo.Blogs")
    .ToList();