ASP.MVC:反映IQueryable而不是Linq to SQL的存储库,DDD如何提问

Zac*_*ott 12 domain-driven-design iqueryable repository linq-to-sql

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

我对此很新,因此显而易见的错误.我是否错过了船,只应从存储库返回完整的实体,而不是投影?在从存储库发送之前,我仍然需要删除Linq to 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>???
}
Run Code Online (Sandbox Code Playgroud)

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

<% Page Inherits="System.Web.Mvc.ViewPage<MyStore.Models.MiniProduct>" %>
Run Code Online (Sandbox Code Playgroud)

编辑:

Cottsak授权代码并使其工作,因此他获得了复选框.然而,Mark Seemann指出这种技术会引起副作用.他在POCO糟糕的投射或子设置中是正确的.在使代码工作之后,我最终制造了一个更多的实体对象,这导致了不必要的复杂化.最终我改变了代码以反映马克的建议.

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

Inherits="System.Web.Mvc.ViewPage<IQueryable<MyStore.Models.MiniProduct>>"
Run Code Online (Sandbox Code Playgroud)

模型字段通过以下方式访问:

Model.SingleOrDefault().ProductNo
Model.SingleOrDefault().Name
...
Run Code Online (Sandbox Code Playgroud)

这导致了一个

foreach (MyStore.Models.MiniProduct myproduct in Model) {}
Run Code Online (Sandbox Code Playgroud)

谢谢你们的答案.

Mar*_*ann 29

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

答案稍长:

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

域对象应以技术中立的方式定义.这最好是作为Plain Old C#Objects(POCO)完成的.

此外,暴露IQueryable使您有机会执行预测.这可能听起来很有吸引力,但在DDD背景下实际上相当危险.

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

例如,考虑实体的概念(不是LINQ to Entitities Entity,而是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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

在任何情况下,如果您经常发现只需要选择域对象的某些部分,则可能是因为它们违反了单一责任原则.将Domain Class分为两个或更多类可能是一个更好的主意.

总之,暴露IQueryable听起来像一个非常有吸引力的策略,但随着L2S的当前实现,它将导致Leaky抽象和贫血领域模型.

使用下一版本的实体框架,我们应该获得POCO支持,这样可能会使我们更接近这一目标.但是,就我而言,陪审团仍然在那个帐户上.


有关非常相似主题的更全面讨论,请参阅IQueryable是紧耦合.

  • 澄清一下:从理论上讲,我发现暴露IQueryable的想法非常具有吸引力,与DDD完全不同.它(当前)在实际实现细节方面失败,因为将实现细节与接口分离起来太困难了.有一天我们可能会到达那里,但我们现在不在那里.引起漏洞抽象的不是IQueryable本身,而是IQueryable中的'T'. (2认同)
  • 我上次检查时,EF4中的'POCO支持'仍然不允许非默认构造函数,所以我还没有改变我的立场. (2认同)
  • 我目前的观点是,不使用关系数据库是解决方案. (2认同)

Mat*_*caj 8

尝试这样的事情:

public class AnnouncementCategory : //...
{
    public int ID { get; set; }
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

..并在你的回购中:

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);
}
Run Code Online (Sandbox Code Playgroud)

这样,我不是投射到匿名类型,而是投射到我AnnouncementCategory班级的新实例中.您可以忽略的GetAnnouncementsByCategory功能,如果你喜欢,这是用来链相关的集合Announcement对象为每个类别,但在某种程度上,使他们可以偷懒满载的IQueryable(即.所以,当我这个属性最终调用,我不必调用整个集合.我可以做更多的LINQ来过滤).