【问题标题】:Entity Framework Disconnected POCO, relationships with huge record count实体框架断开的 POCO,记录数量巨大的关系
【发布时间】:2012-07-03 10:41:47
【问题描述】:

我在分层架构中使用实体框架 POCO。获得实体(y/ies)后,我们应该断开连接工作。
假设我们有两个实体:

public class Customer
{  
  public int Id;  
  public string Name;
  public virtual IList<Order> Orders;
}  

public class Order
{
  public int Id;
  ....
}

假设有一位客户的订单数为 1000。
在这种情况下,我们希望在服务方法中获取该客户仅在本周完成的订单。
Repository 方法和Service 方法应该如何?

编辑

我想我应该扩展我的问题。
首先,我的意思是“有 1000 个订单的客户”,我不想得到那个客户。我想获取“Order.Date > DateTime.Now.AddDays(-7) 的客户订单(具有已知 ID,假设 Customer.Id=5)”。
表示 IQueryable 对象很容易。

public List<Order> GetOrdersInLastWeek(int CustomerId)  
{  
  Customer customer = (from c in ctx.Customers where c.Id == CustomerId).SingleOrDefault();  
  List<Order> ordersThatIWant = customer.Orders.Where(o => o.Date > DateTime.Now.AddDays(-7)).ToList();
  return ordersThatIWant;  
}

我很好奇这些:上面的代码运行良好。
- 但是,对于 ORM/N-Layer 最佳实践是否正确?因为 Order 不是“聚合根”,所以 Customer 是“聚合根”。
- 我是否必须在 CustomerService 类或 OrderService 类中拥有这个 GetOrdersInLastWeek?
- 对我来说最后也是最重要的一个;如果我有像 GetOrdersInLastWeek 这样的方法,以及像这样的其他方法,那么其他层(例如表示层)中的“Customer.Orders”导航属性呢(假设我们正在使用 POCO 实体并断开连接)?
- 另一个例子;我想在服务方法中对 Customer.Orders 导航属性进行分页。我如何以及在哪里可以做到这一点(我知道怎么做,但我的意思是根据最佳实践)?

【问题讨论】:

  • 其他层中的“Customer.Orders”导航属性怎么样”是什么意思?
  • 我不使用额外的 DTO 将 POCO 转换为 UI 层。因此,UI 层看到了 Customer 类的 Orders 导航属性。但是我会在 OrderService 类中对 Orders 进行每个查询,因此在这种情况下,Customer.Orders 在 UI 层中是无用的。这是正确的方法吗?
  • 我不能说它是否正确。我不明白您为什么不加载客户包括您在 UI 层中需要的订单(如下面的示例)?对于其他点,我在下面编辑了我的答案(请参阅“编辑”部分)。

标签: entity-framework orm entity-relationship


【解决方案1】:

为此,您可以尝试混合使用急切加载和显式加载,例如 DbContext (EF => 4.1) 类似:

public List<Customer> GetCustomersWithOrders()
{
    using (var ctx = new MyDbContext())
    {
        var customers1 = ctx.Customers.Include(c => c.Orders)
            .Where(c => c.Orders.Count() < 1000)
            .AsEnumerable();

        var customers2 = ctx.Customers
            .Where(c => c.Orders.Count() >= 1000)
            .AsEnumerable();

        DayOfWeek today = DateTime.Now.DayOfWeek;
        DateTime monday = DateTime.Now.AddDays(-1*(today-DayOfWeek.Monday));
        DateTime sunday = monday.AddDays(6);
        foreach (var customer in customers2)
        {
            ctx.Entry(customer).Collection(c => c.Orders).Query()
                .Where(o => o.ShipDate >= monday && o.ShipDate <= sunday)
                .Load();
        }

        return customers1.Concat(customers2).ToList();
    }
}

结果将由1 + 1 + customers2.Count 数据库查询的结果组成。如果客户有超过 1000 个订单的情况很少见,我认为这些多个查询不会是一个大的性能问题。

编辑

我建议更改您的查询。问题是您加载了一个客户(查询 1),然后触发延迟加载 all 订单(查询 2)。因此,在您在内存中应用过滤器之前,您可能已经加载了超过 1000 个订单。您可以通过以下方式避免这种情况:

public List<Order> GetOrdersInLastWeek(int CustomerId)  
{  
    var date = DateTime.Now.AddDays(-7);
    var orders = ctx.Customers
        .Where(c => c.Id == CustomerId)
        .Select(c => c.Orders.Where(o => o.Date > date))
        .SingleOrDefault();

    if (orders == null)
        return null; // or create an empty list

    return orders.ToList();
}

关于您的问题:

我是否必须在 CustomerService 中有这个 GetOrdersInLastWeek 类,还是 OrderService 类?

CustomerService。因为您正在加载特定客户的数据。但在我看来,使用OrderService 也没有那么错。

我想在 Customer.Orders 导航属性上进行分页 服务方式。我如何以及在哪里可以做到这一点(我知道怎么做,但是 我的意思是根据最佳实践)?

对于您当前的架构,您必须在您的服务方法中执行此操作(使用 SkipTake 创建查询),因为您正在公开内存中的集合,即服务方法的具体化结果。因此,您不应该在服务之外进行分页,因为它会强制您在分页之前加载所有 数据。在我看来,唯一的选择是从服务中返回IQueryable&lt;T&gt;,然后通过在查询中添加SkipTake 在UI 层中进行分页。但这意味着你不能在 UI 层断开连接。

【讨论】:

  • 感谢您的回答,但我的重点是不同的点,所以我扩展/编辑了我的问题
猜你喜欢
  • 1970-01-01
  • 2020-12-06
  • 2013-10-19
  • 1970-01-01
  • 2011-08-21
  • 1970-01-01
  • 2011-04-06
  • 2012-03-08
  • 1970-01-01
相关资源
最近更新 更多