ASP.NET Web API打破了Repository / Services模式

时间:2014-12-04 21:37:15

标签: c# asp.net asp.net-web-api asp.net-web-api2

我是构建Web API的新手,已经达到了可以使用某个方向的程度。举个例子,假设我有Product模型和Attachment模型看起来像这样:

 public class Product
 {
     public Product()
     {
        Attachments = new List<Attachment>();
     }
     public int ID { get; set; }
     public string Name { get; set; }
     public List<Attachment> Attachments { get; set; }
 }

 public class Attachment
 {
     public int ID { get; set; }
     public string Name { get; set; }
 }

请注意,Product模型具有类型为Attachment的属性列表。在初始化AttachmentRepository类中的Property对象时,利用ProductRepository填充此属性是不好的做法?

所以在ProductRepository我会做这样的事情:

Product product = new Product
{
    ID = SomeId,
    Name = SomeName
    Attachments = AttachmentRepository.GetAttachments(SomeName)
};

这是一个糟糕的设计,我接近这个错误吗?我不打算为Attachment创建一个Controller,因为在没有Product的上下文的情况下获取附件没有用例。

2 个答案:

答案 0 :(得分:7)

&#34;我不打算创建一个附件控制器,因为在没有产品上下文的情况下获取附件没有用例。&#34;

反过来怎么样?您是否希望获得没有附件的产品?

我发现尝试做太多而不是可重复使用的方法,因为如果我收到大量产品并且对附件不感兴趣,我不想要开销还获得所有这些产品的附件。然后我后悔设计(无论是我的还是同事),为了获得产品而添加了一种新方法。

在我的控制器中,我将调用GetProduct,然后调用GetAttachments。这将允许您根据需要混合匹配。

另一种方法是在Products存储库中执行此操作,具有仅获取产品的方法(GetProducts),以及调用GetProducts并调用GetAttachments的另一个GetProductsWithAttachments方法。或者你当然可以为, includeAttachments = false)设置一个布尔参数。我不喜欢我的数据库层变得那么混乱。我让控制器成为了把它们拉到一起的地方。

我甚至可能在Products模型中没有Attachments属性,而是具有特定于控制器的ProductResponseModel,它类似于ViewModel。它将是包含产品信息和Attachments属性的那个,并且您将从调用两个单独的Repository方法的结果中填充它。

如果您在数据库层执行所有操作,并且数据库图层中的产品模型具有仅在调用GetProductsWithAttachments时填充的附件属性,则您的产品模型半满随着时间的推移添加许多其他类似的属性,并且您有一个非常令人困惑的模型,因为有时只根据您调用的存储库的哪个方法填充其某些属性。开发人员必须开始挖掘进入回购方法,找出什么填充什么。如果您将促销添加到产品,您将获得GetProductWithAttachments,GetProducts,GetProductWithPromotions,GetProductWithAttachmentsAndPromotions?你可以开始看看这是如何失控的,如果你真的想把它加入你的存储库,一些默认参数会更好。但是:

推荐:控制器中的组成

这就是为什么我的偏好是让控制器协调整个事情。在控制器内单独调用repo方法。不确定您是使用REST样式还是Web API控制器中的内容,所以只需考虑这个伪代码:

public class ProductController: ApiController
{
  public ProductResponseModel Get(int productId) 
  {
     var model = new ProductResponseModel{
        Product = ProductRepository.Get(productId);
     };
     model.Attachments = AttachmentRepository.GetList(model.Product.Name);

     // I could have flattened out the Product into its properties instead of having a model.Product,
     // but that can be a maintenance problem and requires something like AutoMapper to manage well
     return model;
  }

}

public class ProductResponseModel {
  public Product Product {get;set;}
  public IEnumerable<Attachment> Attachments {get;set;}
}

ProductResponseModel是一个组合示例。它创建了与DB层的松散耦合,因此您可以自由地混合和匹配,即撰写模型,了解每个API控制器所需的数据。 ProductResponseModel本身不是非常可重用的,可能只在该控制器内。另一个需要不同产品数据组合的控制器将拥有自己的SomethingResponseModel并调用单独的Repository方法来填充它。 即使我们没有重复使用我们的* ResponseModel,但这并不是一个很大的损失,因为它们是简单的POCO。从我们的repo方法中获得重用更为重要,并且使它们不会变得比它们需要的更复杂。此外,我们仍然使用您的简单模型,例如ProductAttachment存储库层的一部分,以便调用repo的每个人都使用相同的语言并使用相同的常见类型。因此,不要让您的Repo图层返回ProductResponseModel。控制器的工作是Product并填充ProductResponseModel

我使用命名约定* ResponseModel,因为如果我也有一个复杂的action参数参数,那么也有一个* RequestModel。即请求/响应。如果我有一个比REST风格更具RPC样式的API方法,例如ProductController.DiscontinueDistribution,它的返回可能非常专业,所以我有一个ProductDiscontinueDistributionResponseModel。这些都是简单的POCO,就像MVC中的ViewModels一样。

答案 1 :(得分:2)

如果您没有使用EF或其他ORM,那么您的Product对象可能不应包含Attachment个对象的列表 - 这应该是一个构造(如@AaronLS所说)某种形式的DTO类似于MVC中的viewmodel。

类似的东西:

public class DTOProductAttachment
{
    Product Product { get; set; }
    List<Attachment> Attachments { get; set; }

    public DTOProductAttachment(int id, string name)  // <-- Product ID, Product Name
    { 
        Product = ProductRepository.GetProduct(id); // <-- Product ID
        Attachments = AttachmentRepository.GetAttachmentsByProductName(name); // <-- Product Name, or ID or whatever joins your tables
    }; 
}

......我认为这里的重要性是,你的&#34;对象&#34; ProductAttachment应反映您的数据库设计。您的存储库应该以一种直观的架构方式运行。

例如,如果AttachmentsRepository.GetAttachmentsByProductName()(产品名称)是Name表上的外键,则上面的Attachments方法是完全直观的(可能应该是Product.ID但我是&#39 ; m与上面的例子一起运行。)

但是,这些决定与您的设计要求/目标有关。例如,如果一个Attachment对象与另一个对象有一对多关系,那么让我们说AttachmentChild,那么假设您可能想要选择所有AttachmentChild是合理的。 1}} Product名称或ID的对象。

...在这种情况下,添加像AttachmentChildRepository.GetAttachmentsByProductName()这样的方法似乎有点可疑。

对于显着性,您可以在Product对象上包含AttachmentChild名称(或ID),即使它在技术上不是必需的。

我希望这会有所帮助。