12

我想创建一个 DDD 存储库,它返回与 Linq 到 SQL 基础类匹配的 IQueryable 实体,减去任何关系。我可以轻松地返回实体减去与 Linq 选择新 {field, field, ... } 投影的关系。如何编码存储库实体类?我将如何从具有存储库类而不是 Linq to SQL 类的存储库中返回一个对象,并且仍然使用来自 Linq 选择的多个实体填充它?我将如何在我的 ViewModel 中引用这个返回的类?

我对此很陌生,因此有明显的错误。我是否错过了这条船,应该只从存储库中返回完整的实体,而不是投影?在从存储库发送它们之前,我仍然需要去除 Linq 到 SQL 的关系。我完全不在基地吗?我真的很想保留 IQueryable 数据类型。

例如,我的存储库中的 Linq to SQL 代码:

public class MiniProduct
{
    public MiniProduct( string ProductNo, string Name, string Description, double Price)
    {    this.ProductNo = ProductNo;
         this.Name = Name;
         this.Description = Description;
         this.Price = Price;
    }
}

public IQueryable<MiniProduct> GetProductsByCategory( string productCategory)
{
    return ( from p in db.Products
             from c in db.Categories
             from pc in db.ProductCategories
             where c.CategoryName == productCategory &&
                   c.CatID == pc.CatID &&
                   pc.ProductID == p.ProductID
             select new { p.ProductNo, p.Name, p.Description, p.Price } );
    // how to return IQueryable<MiniProduct> instead of IQueryable<AnonymousType>???
}

在视图中(尝试强类型 ViewModel)我的模型数据类型是什么以及如何从视图中引用?

<% Page Inherits="System.Web.Mvc.ViewPage<MyStore.Models.MiniProduct>" %>

编辑:

Cottsak 授权代码并使其工作,因此他赢得了复选框。然而,Mark Seemann 指出,这种技术会产生副作用。他说得对,你的 POCO 是不好的。在使代码正常工作后,我最终制作了大量的一次性实体对象,这导致了不必要的复杂性。最终,我更改了代码以反映 Mark 的建议。

添加到 Cottsak 的建议:我的存储库返回值是 IQueryable。页面指​​令模型引用类型为

Inherits="System.Web.Mvc.ViewPage<IQueryable<MyStore.Models.MiniProduct>>"

模型字段由以下人员访问:

Model.SingleOrDefault().ProductNo
Model.SingleOrDefault().Name
...

这导致了一个

foreach (MyStore.Models.MiniProduct myproduct in Model) {}

谢谢两位的回答。

4

2 回答 2

29

假设您的 LINQ to SQL (L2S) 类是自动生成的并反映了您的底层数据库,简短的回答是:不要公开任何 L2S 类的 IQueryable - 这将是一个泄漏抽象

稍长的答案:

存储库的全部意义在于将数据访问隐藏在抽象之后,以便您可以独立于域模型替换或更改数据访问代码。当您将 Repository 接口基于特定实现(基于 L2S 的数据访问组件 (DAC))中定义的类型时,这是不可能的 - 即使您可以提供 Repository 接口的新实现,您也需要参考您的 L2S DAC。如果您突然决定切换到 LINQ to Entities 或 Azure 表存储服务,那将不会特别好。

域对象应该以技术中立的方式定义。这最好作为普通旧 C# 对象 (POCO) 来完成。

此外,公开 IQueryable 使您有机会执行预测。这听起来很有吸引力,但实际上在 DDD 环境中相当危险。

在 DDD 中,我们应该设计域对象,以便它们封装域逻辑并确保所有不变量。

例如,考虑实体的概念(不是 LINQ to Entitities 实体,而是 DDD 实体)。实体几乎总是由持久 ID 标识,因此一个常见的不变量是必须定义 ID。我们可以像这样编写一个基础实体类:

public abstract class Entity
{
    private readonly int id;

    protected Entity(int id)
    {
        if(id <= 0)
        {
            throw new ArgumentOutOfRangeException();
        }
        this.id = id;
    }

    public int Id
    {
        get { return this.id; }
    }
}

这样的类很好地执行了它的不变量(在这种情况下,ID 必须是正数)。可能在特定类中实现了许多其他更特定于域的不变量,但其中许多很可能与投影的概念不一致:您可能能够定义省略某些属性的投影(例如ID),但它会在运行时崩溃,因为类型的默认值(例如 0 表示 int)将引发异常。

换句话说,即使您只需要域对象的某些属性,也只有将其全部水合才有意义,因为这是满足其不变量的唯一方法。

无论如何,如果您经常发现只需要选择域对象的某些部分,那可能是因为它们违反了单一职责原则。将域类拆分为两个或更多类可能是一个更好的主意。

总之,公开 IQueryable 听起来是一个非常有吸引力的策略,但是随着当前 L2S 的实现,它将导致泄漏抽象和贫血域模型。

随着实体框架的下一个版本,我们应该得到 POCO 的支持,所以它可能会让我们更接近那个目标。但是,就我而言,陪审团仍然没有解决这个问题。


有关非常相似主题的更深入讨论,请参阅IQueryable is Tight Coupling

于 2009-11-09T08:33:37.537 回答
8

尝试这样的事情:

public class AnnouncementCategory : //...
{
    public int ID { get; set; }
    public string Name { get; set; }
}

..并在您的回购中:

public IQueryable<AnnouncementCategory> GetAnnouncementCategories()
{
    return from ac in this._dc.AnnouncementCategories
           let announcements = this.GetAnnouncementsByCategory(ac.ID)
           select new AnnouncementCategory
           {
               ID = ac.ID,
               Name = ac.Text,
               Announcements = new LazyList<Announcement>(announcements)
           };
}

private IQueryable<Announcement> GetAnnouncementsByCategory(int categoryID)
{
    return GetAnnouncements().Where(a => a.Category.ID == categoryID);
}

这样,我不是投影到匿名类型中,而是投影到我的AnnouncementCategory类的新实例中。如果您愿意,您可以忽略该GetAnnouncementsByCategory函数,它用于链接Announcement每个类别的关联对象的集合,但以某种方式可以使用 IQueryable 延迟加载它们(即,当我最终调用此属性时,我不必调用整个集合。我可以对此做更多的 LINQ 来过滤)。

于 2009-11-09T08:41:56.677 回答