【问题标题】:Optimise Linq-to-Sql mapping with one to many lookup使用一对多查找优化 Linq 到 Sql 的映射
【发布时间】:2011-05-19 06:05:58
【问题描述】:

我在使用以下数据结构优化数据查找时遇到问题:

Order
----- 
Id 
Customer 
Date 
... etc


OrderStatus
------
Id
OrderId
Date
UpdatedBy
StatusTypeId
...etc

订单列表页面让我头疼,该页面基本上显示了Orders 的列表。列表中的每个订单摘要都显示了来自Order和当前OrderStatus的一堆字段,即OrderStatus和最新的Date链接到Order。

Order List
-------------------------------------------------------
Order Id | Customer     | Order Date  | CurrentStatus |
-------------------------------------------------------
1        | Someone      |  1.10.2010  | Completed     |
-------------------------------------------------------
2        | Someone else | 12.10.2010  | In Progress   |
-------------------------------------------------------
3        | Whoever      | 17.10.2010  | On Hold       |
-------------------------------------------------------

现在,假设我想列出今年的所有订单。我的存储库获取 Order 对象

var orders = _repository.GetAllOrdersSinceDate(dt);

现在我得到了类似的东西

foreach (Order order in orders)
{
    OrderSummary summary = new OrderSummary();
    summary.Customer = order.Customer;
    summary.Date = order.Date;
    // ...etc

    // problem here!!
    summary.OrderStatus = order.OrderStatus
                .OrderByDescending(s => status.Date).First();
}

所以我最终得到的是在Order 上的SELECT 声明,然后在OrderStatus 上为每个Order 返回一个SELECT 声明。

因此,要显示今年所有记录的摘要,需要大约 20,000 个单独的 SQL 查询,并且需要很长时间才能加载。

有什么好办法解决这个问题吗?

我正在考虑重写数据库以在Order 表中保存当前 OrderStatus,所以我最终得到类似

Order
----- 
Id 
Customer 
Date 
CurrentStatusTypeId
CurrentStatusDate
CurrentStatusUpdatedBy
...etc


OrderStatusHistory
------
Id
OrderId
Date
UpdatedBy
StatusTypeId
...etc

这是我能看到的解决问题的唯一方法,但似乎是一个非常讨厌的解决方案。

这里最好的方法是什么?

【问题讨论】:

    标签: linq-to-sql database-design optimization orm entity-relationship


    【解决方案1】:

    请不要非规范化您的数据库模型来解决您的问题。这只会让事情变得更糟。您可以通过编写返回数据传输对象 (DTO) 列表而不是 LINQ to SQL 实体的服务方法来解决此问题。例如,服务方法可能如下所示:

    public OrderSummary[] GetOrderSummariesSinceDate(DateTime d)
    {
        return (
            from order in this.context.Orders
            where order.Date >= d
            let lastStatus = (
                from status in order.OrderStatusses
                orderby status.Date descending
                select status).First()
            select new OrderSummary
            {
                OrderId = order.Id,
                CustomerName = order.Customer.Name,
                Date = order.Date,
                OrderStatus = lastStatus.StatusType.Name
            }).ToArray();    
    }
    

    注意以下几点:

    • 此代码将在数据库中作为单个 SQL 查询执行。
    • 此方法将返回一个仅包含客户端所需数据的对象,仅此而已。没有Customer 对象,没有OrderStatus 对象。
    • 通过调用ToArray,我们确保此时查询数据库并且不会延迟。

    这三点确保性能最大化,并允许服务层控制执行到数据库的内容。

    我希望这会有所帮助。

    【讨论】:

    • 这有点帮助,但有点没有帮助。我知道我需要做这样的事情,但它充满了问题。我的 Order 对象位于域层中。我的 OrderSummary 对象是我的表示层中 ViewModel 的一部分。我的 OrderRepository 甚至不知道 OrderSummary 存在。我的 OrderRepository 包装了一个通用的 Linq 存储库,这给了我非常有限的控制权。我正在使用 DTO,但除了带有 GetCurrentStatus() 方法的 Order 对象(这是我自己的 Order 类,不是 Linq 生成的类)之外,我仍然无法返回太多。
    • 您可以将OrderSummary 移动到服务层,或者定义一个 DTO 并将其映射到您的表示层中的 ViewModel。您需要在此级别使用 DTO 才能获得良好的性能。如果您的存储库受到限制,也许您应该在那里采取不同的方法。例如,在您的存储库上实现 IQueryable。在这里寻找灵感:bit.ly/bF7jL3.
    【解决方案2】:

    您可以按如下方式创建 DataLoadOptions 对象:

    DataContext db = new DataContext
    DataLoadOptions ds = new DataLoadOptions();
    ds.LoadWith<OrderStatus>(c => c.Orders);
    db.LoadOptions = ds;
    

    然后,当您运行查询时,它应该预取 OrderStatus 表

    【讨论】:

    • 这确实提高了性能,但仍有改进的余地。 LoadWith 将从数据库中获取每个Order 的所有OrderStatus 记录,而每个订单只需要最后一个OrderStatus。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多