我是构建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
的上下文的情况下获取附件没有用例。
答案 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方法中获得重用更为重要,并且使它们不会变得比它们需要的更复杂。此外,我们仍然使用您的简单模型,例如Product
和Attachment
存储库层的一部分,以便调用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; Product
和Attachment
应反映您的数据库设计。您的存储库应该以一种直观的架构方式运行。
例如,如果AttachmentsRepository.GetAttachmentsByProductName()
(产品名称)是Name
表上的外键,则上面的Attachments
方法是完全直观的(可能应该是Product.ID但我是&#39 ; m与上面的例子一起运行。)
但是,这些决定与您的设计要求/目标有关。例如,如果一个Attachment
对象与另一个对象有一对多关系,那么让我们说AttachmentChild
,那么假设您可能想要选择所有AttachmentChild
是合理的。 1}} Product
名称或ID的对象。
...在这种情况下,添加像AttachmentChildRepository.GetAttachmentsByProductName()
这样的方法似乎有点可疑。
对于显着性,您可以在Product
对象上包含AttachmentChild
名称(或ID),即使它在技术上不是必需的。
我希望这会有所帮助。