【问题标题】:N-tier Repository POCOs - Aggregates?N 层存储库 POCO - 聚合?
【发布时间】:2012-10-10 20:56:02
【问题描述】:

假设以下简单的 POCO、国家和州:

public partial class Country
{
    public Country()
    {
        States = new List<State>();
    }
    public virtual int CountryId { get; set; }
    public virtual string Name { get; set; }
    public virtual string CountryCode { get; set; }
    public virtual ICollection<State> States { get; set; }
}

public partial class State
{
    public virtual int StateId { get; set; }
    public virtual int CountryId { get; set; }
    public virtual Country Country { get; set; }
    public virtual string Name { get; set; }
    public virtual string Abbreviation { get; set; }
}

现在假设我有一个看起来像这样的简单存储库:

public partial class CountryRepository : IDisposable
{
    protected internal IDatabase _db;

    public CountryRepository()
    {
        _db = new Database(System.Configuration.ConfigurationManager.AppSettings["DbConnName"]);
    }

    public IEnumerable<Country> GetAll()
    {
        return _db.Query<Country>("SELECT * FROM Countries ORDER BY Name", null);
    }

    public Country Get(object id)
    {
        return _db.SingleById(id);
    }

    public void Add(Country c)
    {
        _db.Insert(c);
    }

    /* ...And So On... */
}

通常在我的 UI 中,我不会显示所有子项(状态),但会显示汇总计数。所以我的国家列表视图模型可能如下所示:

public partial class CountryListVM
{
    [Key]
    public int CountryId { get; set; }
    public string Name { get; set; }
    public string CountryCode { get; set; }
    public int StateCount { get; set; }
}

当我直接在我的 UI 层中使用底层数据提供程序(实体框架、NHibernate、PetaPoco 等)时,我可以轻松地执行以下操作:

IList<CountryListVM> list = db.Countries
    .OrderBy(c => c.Name)
    .Select(c => new CountryListVM() {
        CountryId = c.CountryId,
        Name = c.Name,
        CountryCode = c.CountryCode,
        StateCount = c.States.Count
    })
    .ToList();

但是当我使用存储库或服务模式时,我会抽象出对数据层的直接访问。似乎我的选择是:

  1. 使用填充的 States 集合返回 Country,然后在 UI 层中映射。这种方法的缺点是我返回的数据比实际需要的多。

    -或-

  2. 将我所有的视图模型放入我的 Common dll 库中(而不是将它们放在我的 MVC 应用程序的 Models 目录中)并扩展我的存储库以返回特定的视图模型,而不仅仅是域 pocos。这种方法的缺点是我将 UI 特定的东西(MVC 数据验证注释)泄漏到我以前干净的 POCO 中。

    -或-

  3. 还有其他选择吗?

你是如何处理这些类型的事情的?

【问题讨论】:

    标签: c# asp.net repository-pattern poco


    【解决方案1】:

    这真的取决于我们所做的项目架构。但是通常......我们在存储库之上为您处理此逻辑的服务。该服务决定使用哪些存储库来加载哪些数据。流程是 UI -> 控制器 -> 服务 -> 存储库 -> 数据库。 UI 和/或控制器不了解存储库或其实现。

    另外,StateCount = c.States.Count 毫无疑问无论如何都会出现在美国列表中.. 不是吗?我很确定它会在 NHibernate 中(通过 LazyLoading 导致额外的选择被发送到数据库)。

    【讨论】:

    • 已经有一段时间了,但我想当我查看 StateCount = c.States.Count 的跟踪时,它创建了一个聚合而不是加载集合。我再去看看是不是这样。
    【解决方案2】:

    一种选择是将您的查询与现有基础架构完全分开。这将是CQRS 设计的实现。在这种情况下,您可以使用“精简读取层”直接向数据库发出查询,绕过您的域对象。您现有的对象和 ORM 实际上阻碍了您的工作,而 CQRS 允许您拥有一个独立的“命令端”,并且可能与您的“查询端”拥有完全不同的技术集,其中每个对象都旨在完成自己的工作不受对方要求的影响。

    是的,我的意思是直接从 MVC 控制器中直接使用 Dapper 之类的东西来执行此操作(谨防未经测试的代码示例),例如:

    int count = 
        connection.Query<int>(
            "select count(*) from state where countryid = @countryid", 
            new { countryid = 123 } );
    

    【讨论】:

    • 老实说,基础设施还处于起步阶段。我乐于尝试新事物。我试图接受整个 n 层抽象层的东西,但似乎无论我做什么,层之间都有泄漏。今晚晚些时候我会看看 CQRS 的东西。我目前使用的 ORM 是 NPoco(PetaPoco 的分支)。它类似于 Dapper,并且非常灵活。阻碍进步的是存储库模式的想法,并为可测试性而抽象一切。除了最简单的例子恕我直言,这是一个主要的 PITA。
    • 如果您通过定义聚合边界将 DDD 样式应用于域对象持久性,并确保您的存储库合同仅允许 Get(id) 和 Add(object),并保留任何和所有查询到一个完全独立的垂直切片(有它自己的层,但可能只有一两个而不是典型的数量)那么你应该是金色的。
    【解决方案3】:

    老实说,你的问题让我思考了几天。我越来越倾向于认为非规范化是正确的解决方案。

    看,领域驱动设计的要点是让问题领域驱动您的建模决策。考虑现实世界中的国家实体。一个国家有一个州列表。但是,当您想知道某个国家有多少个州时,您并不是要翻阅百科全书中的州列表并计算它们。您更有可能查看该国家/地区的统计数据并查看那里的州数。

    恕我直言,同样的行为应该反映在您的域模型中。您可以在国家的属性中拥有这些信息,或者引入一种 CountryStatistics 对象。无论您选择哪种方法,它都必须是国家总量的一部分。处于聚合的一致性边界将确保它在添加或删除状态时保持一致的数据。

    【讨论】:

      【解决方案4】:

      其他一些方法:

      • 如果预计 states 集合不会发生太大变化,您可以 允许一些非规范化 - 将“NumberOfStates”属性添加到 国家对象。它将优化查询,但您必须 确保额外的字段包含正确的信息。

      • 如果您使用 NHibernate,则可以使用 ExtraLazyLoading - 它会 发出另一个选择,但不会填充整个集合 计数被调用。更多信息在这里: nHibernate Collection Count

      【讨论】:

      • 在您的第二种方法中,这不会导致 n+1 选择(选择获取国家/地区列表,然后选择每个国家/地区)?
      • 是的,afaik 它将为每个国家/地区发出计数查询。如果您有多个国家/地区,这不是一个好的解决方案。
      猜你喜欢
      • 2011-04-02
      • 2012-01-02
      • 1970-01-01
      • 2010-09-30
      • 2014-06-27
      • 1970-01-01
      • 2011-07-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多