【问题标题】:Linq2Sql - How do you lazy load nested lists?Linq2Sql - 你如何延迟加载嵌套列表?
【发布时间】:2010-11-19 12:05:15
【问题描述】:

如何在不实际执行查询的情况下延迟加载嵌套列表?使用一个非常基本的示例,假设我有:

class CityBlock {
     IList<Building> BuildingsOnBlock;
     Person BlockOwner;
}

class Building {
     IList<Floor> FloorsInBuilding;
}

class Floor {
     IList<Cubicle> EmployeeCubicles;
}

Class Cubicle {
     System.Guid CubicleID;
     Person CubicleOccupant;
}

然后在我的存储库层中,我有以下方法:

GetCityBlocks()

然后在服务层中,我将使用 GetCityBlocksByOwner,在这里我使用扩展方法来获取特定人拥有的城市街区,比如我们只想要 Guido 的街区:

GetCityBlocks().ForOwner("Guido")

如果我们在存储库中执行 .ToList() ,它将执行查询 - 这将是荒谬的,因为我们不知道我们在那个级别获得了谁的块。所以问题是,我们如何有效地做到这一点?

假设有 50000 个街区所有者,以及大约 1000000 个城市街区,加载所有这些街区不是一种选择。由于嵌套,使用 IQueryables 将不起作用(至少我知道没有极端的黑客攻击)。此外,如果我尝试使用 Rob Conery 的 LazyList 之类的东西,那么我们的 DAL 基本上会泄漏到我们的域模型中,这在未来可能会非常糟糕。

那么我该如何正确地做到这一点呢?

  • 是否确定 正确的上下文?如果是这样,我们会这样做 this 在存储库层中,或 服务层?
  • 我是否将服务层和我的存储库层半融合在一起以获得 非常具体的服务方式?
  • 还是我完全遗漏了什么? (对于 Linq2Sql 来说还是比较新的 正在被淘汰的东西 无论如何……)

编辑: 在存储库模式中,我们目前正在映射到我们的域对象,所以它看起来像这样:

public IQueryable<CityBlock> GetCityBlocks(){
    var results = from o in db.city_blocks
                  let buildings = GetBuildingsOnBlock(o.block_id)
                  select new CityBlock {
                      BuildingsOnBlock = buildings,
                      BlockOwner = o.block_owner
                  };
    return results;
}

为此,我们必须让建筑物获得 .ToList(),除非我们将 CityBlock 对象中的实际字段设置为 IQueryable - 这似乎不正确,因为它似乎太多了有权访问 CityBlock.BuildingsOnBlock 字段的任何人。这种映射到我们的领域对象是我们应该在服务层中做的吗?

【问题讨论】:

  • 整个淘汰的事情是一个红鲱鱼。虽然它确实不会在改进方面引起太多关注,但它已经在框架中,所以它会伴随我们一段时间。无论如何,StackOverflow 使用 Linq to SQL,我会说它工作得很好。
  • 您从 GetCityBlocks() 方法返回了什么?由于您使用的是扩展过滤器,我猜您已经返回 IQueryable 对吗?否则你真的应该使用罗伯特的答案。请注意,从您的存储库中公开 IQueryable 意味着只公开 Linq 而不是 Linq2Sql

标签: .net linq-to-sql repository-pattern lazy-loading service-layer


【解决方案1】:

您通过返回 IQueryables 而不是 ILists 来做到这一点。

ToList() 导致查询立即执行,因为必须执行从 IQueryable 到 IList 的转换。

只要您返回 IQueryables,延迟加载就应该推迟执行,直到实际需要数据,即调用 ToList() 时。

目前我找不到参考资料,但据我了解,如果您这样做,linq to sql 有机会优化它发送到服务器的 SQL。也就是说,它最终会读取这些记录:

GetCityBlocks().ForOwner("Guido")

而不是这些记录:

GetCityBlocks()

【讨论】:

  • 但是在域模型中使用 IQueryables 似乎是一个问题,因为您这样做只是为了执行 DAL,而不是因为它是该域模型的一部分。 IQueryable 不一定是查询该对象中的那些字段/属性/类对象所需的东西。还是我仍然在错误地看待这个问题?
  • 有一种观点认为,如果您将 IQueryables 返回到服务层,那么服务层就没有“定义明确的接口”(您赋予它太多的权力来操纵询问)。所以有一个权衡。它将使您的存储库更简单(因为您不需要为每个可能的查询提供方法),并且它确实具有做您想做的事情的优点。我不是模式律师,所以我不会告诉你你不能这样做,但其他人可以。
  • 我同意你的观点,稍微放松一下控制,但是如果我们映射到存储库中我们自己的对象呢?这是我们应该在其他地方做的事情吗? (见我的编辑)
  • 如果您在 GetBuildingsOnBlock 中返回 IQueryable,您仍然可以通过仅返回所需字段并在其底层 Linq 查询中包含“where”子句来限制它,所以我会说您也没有公开很多。消费者(服务层)唯一能做的就是更多地约束它;它不像它可以扩展您的 IQueryable 结果。
  • 那么我们将在哪里进行从 linq 模型到我们的域模型的实际绑定呢? GetBuildingsOnBlock() 应该创建建筑对象(或对它们的查询),这将是 list,而每个都有一个 List。由于查询尚未执行,Linq 将不知道为这些对象使用什么 ID。
【解决方案2】:

您可以尝试不同的方法来映射您的域对象以使其正常工作。问题是,无论您做什么(除非您在域对象中将列表更改为 IQueryables),您在映射时都会以 ToList() 结束。

另一种方法是让 linq2Sql 通过创建自定义数据上下文而不使用设计器进行映射来映射到您的 POCO :),这样您就可以保持域模型的清洁并让 linq2Sql 在正确的时间填充依赖项.请注意,进入这条路线有其自身的问题,但可以做到。

这里有一个链接可以让你开始这条路线

Achieving POCO s in Linq to SQL

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多