【问题标题】:Ad hoc data and repository pattern即席数据和存储库模式
【发布时间】:2009-03-07 08:11:35
【问题描述】:

从不适合任何模型实体或扩展某些模型实体的存储库返回临时(逐个自定义)数据的推荐方法是什么?

101 示例是无处不在的 hello word 应用程序:一个博客系统。假设您要加载一个帖子列表,其中帖子条目具有一些 Post 实体中不存在的附加信息。假设它是 cmets 的数量以及最后评论的日期和时间。如果使用普通的旧 SQL 并直接从数据库读取数据,这将是非常微不足道的。如果我负担不起为每个帖子加载整个评论集合,并且我想在一次数据库命中中完成,我应该如何使用存储库模式以最佳方式完成它?这种情况有没有常用的模式?现在假设您有一个中等复杂的 Web 应用程序,其中每个页面需要稍微不同的自定义数据,并且无法加载完整的层次结构(性能、内存要求等)。

一些随机的想法:

  1. 为每个模型添加一个属性列表,可以由自定义数据填充。

  2. 逐个子类模型实体,并为每个子类创建自定义阅读器。

  3. 使用 LINQ、编写即席查询和读取匿名类。

注意:我问了一个similar question recently,但是好像太笼统,没有引起太多关注。

示例:

根据以下答案中的建议,我添加了一个更具体的示例。这是我试图描述的情况:

IEnumarable<Post> posts = repository.GetPostsByPage(1);
foreach (Post post in posts)
{

    // snip: push post title, content, etc. to view

    // determine the post count and latest comment date
    int commentCount = post.Comments.Count();
    DateTime newestCommentDate = post.Comments.Max(c => c.Date);

    // snip: push the count and date to view

}

如果我不做任何额外的事情并使用现成的 ORM,这将导致 n+1 个查询或可能一个查询加载所有帖子和 cmets。但最佳情况下,我希望能够只执行一个 SQL,该 SQL 将为每个帖子返回一行,包括帖子标题、正文等以及评论计数和最近的评论日期。这在 SQL 中是微不足道的。问题是我的存储库将无法读取此类数据并将其放入模型中。最大日期和计数在哪里?

我不是在问如何做到这一点。您总是可以以某种方式做到这一点:向存储库添加额外的方法、添加新类、特殊实体、使用 LINQ 等,但我想我的问题如下。为什么存储库模式和正确的模型驱动开发如此广泛接受,但它们似乎并没有解决这种看似非常普遍和基本的情况。

【问题讨论】:

    标签: sql database model repository-pattern


    【解决方案1】:

    这个问题有很多。您是否需要这些特定数据来进行报告程序?如果是这样,那么正确的解决方案是为报告目的单独访问数据。扁平化的数据库、视图等。

    或者它是一个临时查询需要?如果是这样,Ayende 有一个关于这个问题的帖子。 http://ayende.com/Blog/archive/2006/12/07/ComplexSearchingQueryingWithNHibernate.aspx

    他使用了一个“Finder”对象。他正在使用 NHibernate,所以基本上他所做的是创建一个分离的查询。

    我过去做过类似的事情,通过创建一个 Query 对象,我可以在将它交给存储库之前填充它(一些 DDD 纯粹主义者会反对它,但我发现它优雅且易于使用)。

    Query 对象实现了一个流畅的接口,所以我可以这样写并返回结果:

    IQuery query = new PostQuery()
       .WithPostId(postId)
       .And()
       .WithCommentCount()
       .And()
       .WithCommentsHavingDateLessThan(selectedDate);
    
    
    Post post = _repository.Find(query);
    

    但是,在您的具体情况下,我不得不怀疑您的设计。你是说你不能用帖子加载 cmets。为什么?您是否只是对性能过于担心?这是过早优化的情况吗? (在我看来是这样)

    如果我有一个 Post 对象,它将是我的聚合根,它会附带评论。然后你想做的所有事情都会在每种情况下都有效。

    【讨论】:

    • 谢谢。你的建议似乎是一个好的开始。我想知道您实际上将评论计数存储在哪里?当然,在 Post 实体中没有单独的数据成员。
    • 关于性能,博客文章中的例子只是一个例子。我想到的真正的应用程序已经在运行,我们真的无法负担加载整个集合。
    • 真正的问题域会产生影响。任何人都无法判断您的域是否存在设计缺陷,这实际上可能会带来更好的解决方案。帖子和评论是一个解决问题,您的问题没有上下文意义。
    【解决方案2】:

    由于我们需要紧急解决我在原始问题中概述的问题,因此我们采用了以下解决方案。我们为每个模型实体添加了一个属性集合(字典),如果 DAL 需要,它会将自定义数据粘贴到其中。为了建立某种控制,属性集合由指定类的实例作为键,它只支持简单的数据类型(整数、日期等),这是我们在移动时所需要的,而且很可能永远需要.这解决的一个典型情况是:加载一个实体,其中包含其子集合的计数,而不是完全填充的集合。我怀疑这可能不会因为软件设计而获得任何奖励,但它是我们案例中最简单、最实用的解决方案。

    【讨论】:

    • 我认为另一种选择是将一些命名查询附加到实体并使用存储库中的那些。不过很有趣的问题,太糟糕了,似乎很少有人能理解它。
    【解决方案3】:

    不能说我真的明白问题出在哪里,只是在这里开枪:

    • 添加一个特定的实体来封装你想要的信息
    • 为帖子添加属性评论。 (我不明白为什么这需要您获取所有 cmets - 您可以为您正在加载的特定帖子获取 cmets)
    • 使用延迟加载仅在您访问属性时获取 cmets

    我认为,如果您将平台、语言和 O/R 映射器具体化(似乎是 .NET C# 或 VB,因为您提到了 LINQ。LINQ 2 SQL?实体框架?还有什么?)

    【讨论】:

    • 感谢您指出这一点。我添加了一个简单的具体示例和更多解释。
    【解决方案4】:

    如果您没有被锁定在 RDBM 中,那么像 CouchDB 或 Amazons SimpleDB 这样的数据库可能值得一看。您所描述的在 CouchDB 视图中是微不足道的。这可能并不能真正回答您的具体问题,但有时最好看看完全不同的选项。

    【讨论】:

      【解决方案5】:

      为此,我通常有一个 RepositoryStatus 和一个用作我的数据传输对象 (DTO) 的 Status 类。 Status 类用于我的应用程序服务层(出于相同的原因),RepositoryStatus 从该层继承。然后使用这个类,我可以从 Repository 层返回错误消息、响应对象等。这个类是通用的,因为它将接受任何对象并将其投射给接收者。

      这里是状​​态类:

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using RanchBuddy.Core.Domain;
      using StructureMap;
      
      namespace RanchBuddy.Core.Services.Impl
      {
          [Pluggable("Default")]
          public class Status : IStatus
          {
              public Status()
              {
                  _messages = new List<string>();
                  _violations = new List<RuleViolation>();
              }
      
              public enum StatusTypes
              {
                  Success,
                  Failure
              }
      
              private object _object;
              public T GetObject<T>()
              {
                  return (T)_object;
              }
              public void SetObject<T>(T Object)
              {
                  _object = Object;
              }
      
              private List<string> _messages;
              public void AddMessage(string Message)
              {
                  _messages.Add(Message);
              }
              public List<string> GetMessages()
              {
                  return _messages;
              }
              public void AddMessages(List<string> Messages)
              {
                  _messages.AddRange(Messages);
              }
      
              private List<RuleViolation> _violations;
              public void AddRuleViolation(RuleViolation violation)
              {
                  _violations.Add(violation);
              }
              public void AddRuleViolations(List<RuleViolation> violations)
              {
                  _violations.AddRange(violations);
              }
              public List<RuleViolation> GetRuleViolations()
              {
                  return _violations;
              }
              public StatusTypes StatusType { get; set; }
          }
      }
      

      这里是 RepositoryStatus:

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using RanchBuddy.Core.Services.Impl;
      using StructureMap;
      
      namespace RanchBuddy.Core.DataAccess.Impl
      {
          [Pluggable("DefaultRepositoryStatus")]
          public class RepositoryStatus : Status, IRepositoryStatus
          {
      
          }
      }
      

      如您所见,RepositoryStatus 还没有做任何特殊的事情,它只依赖于 Status 对象实用程序。但我想保留以后延期的权利!

      我敢肯定,有些顽固分子会说,如果你想成为一个纯粹主义者,就不应该使用它......但是我知道你的痛苦,有时你需要传递的不仅仅是一个返回对象!

      【讨论】:

        猜你喜欢
        • 2010-12-29
        • 1970-01-01
        • 1970-01-01
        • 2023-04-10
        • 1970-01-01
        • 2012-05-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多