【问题标题】:Querying from the Controller a List<T> obtained fromt the repository increase coupling?从控制器中查询从存储库获得的 List<T> 增加耦合?
【发布时间】:2011-04-13 12:59:17
【问题描述】:

我有一个用 C# 编码的 ASP.NET MVC 应用程序。应用程序的结构如下:

  1. 控制器

  2. 存储库

  3. LINQ to Entities(实体框架)

  4. 查看

我使用存储库 (_ProductRep) 查询 LINQ to Entities 并将实际实体或 List&lt;T&gt; 提供给控制器,而不是 IQueriables&lt;T&gt;

对于我有更多疑问的情况,我想获得一些帮助。我有以下代码:

List<Monthly_Report> lproduct_monthlyReport = _ProductRep.GetArchiveReport(product.Prod_ID, lmonth, lyear);

得到lproduct_monthlyReport 后,我需要在 foreach 中查询它并获取特定记录。目前我实现了这样的解决方案:

foreach (var item in litemList)
{    
   var lproductItem_monthlyReport = lproduct_monthlyReport.Single(m => m.Item_ID == item.Item_ID);
   // Other code
} 

其中litemList 是产品可以拥有的所有可能项目的列表。

我想知道这个解决方案是否明智地增加了耦合(并且违反了law of Demeter)或者它是可以接受的,因为我实际上是在查询List&lt;T&gt;而不是IQueriable&lt;T&gt;。如果我错了,请纠正我,但我猜由于 List 不需要访问 EF DataContext,因此 Controller 和 EF 之间没有耦合。

万一我错了,我能想到的唯一解决方案是用存储库方法替换查询(我仍然必须实现):

var lproductItem_monthlyReport_ProductRep.GetArchiveReport(product.Prod_ID, lmonth, lyear, item.Item_ID);

使用此解决方案,但是存储库在每个循环周期中使用 4 个条件进行一次查询,而在之前的解决方案中,存储库仅使用一个条件进行查询。

请您就这个问题给我指点一下吗?谢谢。

PS:我在循环中需要两个变量 lproduct_monthlyReportlproductItem_monthlyReport,我不能只使用其中一个

PPS:我知道我应该在 Controller 和 Repository 之间建立一个业务服务层,这是我的下一步。

【问题讨论】:

    标签: c# entity-framework asp.net-mvc-3 repository-pattern loose-coupling


    【解决方案1】:

    从存储库返回列表会给你带来糟糕的性能,因为你失去了延迟执行的行为。基本上,您的存储库会将每条记录(而不是相关实体)检索到内存中,并将它们转换为列表,然后在内存中进行处理。如果要访问相关实体,则需要另一个数据库命中。如果您坚持使用 IEnumerable(或 IQueryable),那么您将向客户端隐藏实体框架行为的细微差别,但仍会获得延迟加载和延迟执行等优势。

    暂时忽略你的存储库的细节,如果你这样做:

    List<Product> products = MyEntities.Products.ToList();
    
    Product product1 = products.Single(p => p.Id = 1);
    

    它的表现会比这差得多:

    IEnumerable<Product> products = MyEntities.Products;
    
    Product product1 = products.Single(p => p.Id = 1);
    

    第一个将在没有 WHERE 子句的数据库中执行 SELECT,然后为每个结果实例化 .Net 对象,然后查询该内存列表。在您访问 product1 上的属性之前,第二个将不执行任何操作,此时将发出数据库命令以仅检索 1 个产品,并仅实例化该 1 个产品。

    两者之间的差异对于小数据集可能并不明显,但随着数据集变大,这种差异会越来越严重。加入一个连接的实体(或更糟糕的实体集合),您可能会获得数千个数据库命中,如果您坚持使用 IEnumerable,您将获得 1 个。

    【讨论】:

    • 我不同意这一点。我建议从 DAL 返回列表。例如调用 products.Single(p => p.Id = 1);在没有列表的循环中,每次迭代都会导致一个数据库调用。
    • @RichardW1001:感谢您的回答。存储库模式规定存储库只应返回实际实体。然后您应该足够灵活,以了解您可以在哪里放松特定的处方。但是,通过返回 IQueriable(或 IEnumerable),您需要打开 LINQtoSQL 上下文,而我使用 using 指令打开数据上下文(仅在指令区域内有效)
    • 是的,那太可怕了……但这与我所说的相反!我的意思是,如果您要从存储库中返回一组项目,请将其保留为 IEnumerable 或 IQueryable 而不是 List,以便保持延迟执行。我要说明的一点是,如果您返回一个列表,则数据库调用已经进行,并且只能在内存中进行细化。但是,如果返回 IEnumerable,则可以在进行数据库调用之前向查询添加更多过滤器,同时保持 EF 的分离和无知。
    • 如果 DataContext 在 DAL 函数中被(正确)处理,则不能将其保留为 IEnumerable 或 IQueryable。另外我不建议从 DAL 发送 Linq 实体,而是使用 DTO
    • 我是否遗漏了什么——为什么这么快就处理掉上下文?您确实很欣赏上下文和连接是完全独立的东西吗?并且创建一个新的上下文有相当大的开销?您不应该让它们闲置,但绝对应该让它们停留足够长的时间,以便以最有效的方式正确检索数据!建议你启动一个快速的控制台应用程序,并排通过这两种方法,使用跟踪/分析器观察每个方法的数据库活动 - 这将比文字解释得更好。
    【解决方案2】:

    我可能会有像 GetArchiveReport(int prodID, int lmonth, int lyear, IEnumerable&lt;int&gt; itemIDs) 这样的函数,它会在您的查询中执行 itemIDs.Contains(tbl.ID)

    var SelectedReports = _ProductRep.GetArchiveReport(product.Prod_ID, lmonth, lyear, litemList.Select(item => item.Item_ID));
    foreach(var prodItem in SelectedReports)
    {
      //Do code
    }
    

    【讨论】:

    • 感谢您的回答。但是这些功能有什么用?使用 litemList.Select(item => item.Item_ID) 您实际上可以进行投影。能否请您更好地解释您的解决方案?
    猜你喜欢
    • 2013-10-05
    • 1970-01-01
    • 2018-07-26
    • 1970-01-01
    • 2012-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多