在 MVC (CRUD) 中创建模型的最佳实践
Best practice for creating models in MVC (CRUD)
因此我们向团队添加了一名新程序员,他对如何在 MVC 中创建模型有一些想法,这与我们之前创建模型的方式不同。比方说,我们有一个系统,用户可以在其中提交文档请求,并且该系统中有一个页面,用户可以在该页面上计算完成该文档请求的费用。这个费用创建页面可以输入一些关于费用的数据以及相关的发票。用户可以添加发票行项目并使用它们来自动计算费用总额。在那种情况下,我们通常会创建如下模型。
public class Fee
{
public virtual Guid RequestID { get; set; }
public virtual Guid FeeID { get; set; }
public string FeeTitle { get; set; }
public decimal FeeAmount { get; set; }
public DateTime? DueDate { get; set; }
public decimal AmountPaid { get; set; }
public Invoice Invoice { get; set; }
public List<InvoiceLineItem> LineItems { get; set; }
}
public class Invoice
{
// Additional Invoice Data (To, From, etc)
}
public class InvoiceLineItem
{
public string LineItemTitle { get; set; }
public int Quantity { get; set; }
public decimal PricePerUnit { get; set; }
public decimal Subtotal { get; set; }
}
我们的新程序员认为这不是一个好的方法,因为不同的操作需要不同的数据。例如,当您创建费用时,您需要知道相应的请求 ID。但是,当您更新费用时,您只需要知道 FeeID。因此,当他创建他的模型时,他以这样一种方式创建它们,即存在多层继承,以努力控制在服务层中更新并呈现在视图上的数据。他的想法是,我们应该能够假设为交易传递的任何模型都应该使用其所有数据点,而不必根据情况猜测数据是什么。
对我来说,这给我们的模型增加了大量不必要的复杂性,并使得在其他模块上使用它们变得更加困难。下面的示例说明了这一点。
/// <summary>
/// This model is used to present data in a read fashion to the end user
/// </summary>
public class FeeViewModel : FeeModel_Create
{
public string FullRequestNumber { get; set; }
public decimal Balance { get; set; }
public List<String> States { get; set; }
public List<FeeAttachmentEditModel> Attachments { get; set; }
public List<PaymentViewModel> Payments { get; set; }
}
/// <summary>
/// This model adds a request id to the fee update model because we need to know which request this fee is associated with
/// </summary>
public class FeeModel_Create : FeeModel_Update
{
public Guid RequestID { get; set; }
}
/// <summary>
/// Represents the parameters required to update a fee
/// </summary>
public class FeeModel_Update
{
public virtual Guid FeeID { get; set; }
public decimal FeeAmount { get; set; }
public DateTime? DueDate { get; set; }
public string FeeTitle { get; set; }
public decimal AmountPaid { get; set; }
public List<MaterialList> MaterialTypes { get; set; }
public List<InvoiceLineItem_Adhoc> LineItems { get; set; }
public Invoice Invoice { get; set; }
public void InjectValuesIntoInvoiceModel(Invoice Invoice)
{
Invoice.Description = this.Invoice.Description;
Invoice.Terms = this.Invoice.Terms;
Invoice.To_Name = this.Invoice.To_Name;
Invoice.To_Address = this.Invoice.To_Address;
Invoice.To_Address2 = this.Invoice.To_Address2;
Invoice.To_City = this.Invoice.To_City;
Invoice.To_State = this.Invoice.To_State;
Invoice.To_Zip = this.Invoice.To_Zip;
Invoice.From_Name = this.Invoice.From_Name;
Invoice.From_Address = this.Invoice.From_Address;
Invoice.From_Address2 = this.Invoice.From_Address2;
Invoice.From_City = this.Invoice.From_City;
Invoice.From_State = this.Invoice.From_State;
Invoice.From_Zip = this.Invoice.From_Zip;
}
}
public class InvoiceLineItem_Adhoc
{
public string Type { get; set; }
public string EnteredBy { get; set; }
public decimal Quantity { get; set; }
public decimal UnitCost { get; set; }
public InvoiceLineItem ToLineItem(Guid InvoiceID)
{
var lineItem = new InvoiceLineItem();
StaticValueInjecter.InjectFrom(lineItem, this);
lineItem.InvoiceLineItemID = Guid.NewGuid();
lineItem.InvoiceID = InvoiceID;
lineItem.UserID = 1;
return lineItem;
}
}
public class PaymentViewModel
{
public Guid RequestID { get; set; }
public Guid FeeID { get; set; }
public string FullRequestNumber { get; set; }
public string FeeTitle { get; set; }
public virtual Guid PaymentID { get; set; }
public decimal PaymentAmount { get; set; }
public Nullable<System.DateTime> DatePaid { get; set; }
}
public class FeeAttachmentEditModel
{
public Guid RequestID { get; set; }
public Guid FeeID { get; set; }
public string FullRequestNumber { get; set; }
public string FeeTitle { get; set; }
public virtual System.Guid FeeAttachmentID { get; set; }
public System.Guid AttachmentTypeID { get; set; }
public string AttachmentName { get; set; }
public byte[] Data { get; set; }
public string Extension { get; set; }
public string mimeType { get; set; }
public string AttachmentBody { get; set; }
public HttpPostedFileBase FileUpload { get; set; }
public string FileName { get; set; }
public bool HadError = false;
}
我只是想在这里寻找关于在 MVC 中创建模型的最佳实践的答案。您是否应该创建单独的模型,无论是通过继承部分 类 还是其他方式来适应您正在进行创建、读取、更新或删除的操作。还是让一个视图模型转换为从视图中呈现的内容 on/passed 以及在访问数据时过滤掉来自视图模型的重要内容的逻辑更好?
我们采用的典型方法是拥有一个与视图紧密耦合且仅包含该信息的 ViewModel。 InputModels 也是如此,它们应该只包含将传入的属性。至于继承部分,我会远离这种方法。只需创建简单、扁平的 DTO 并从您的域模型映射它们。应该没有逻辑,所以 DRY 并不真正适用于您应用程序的这一层。
根据第一个答案,我们还没有看到如何在页面上使用视图模型。正如他所说,视图模型应该只包含满足显示视图的数据。您不应该只是盲目地将域模型中的每个字段复制到视图模型中。
我也不喜欢视图模型中有 HasError 标志。使用数据注释或继承自 IValidateableObject 对 POST 执行验证。
我也不认为您需要像在视图模型中那样注入值。如果您应该能够从 linq 查询或 web 服务请求直接投影到您的视图模型中。
最后一点是,对于页面上的下拉菜单,只有 return 您需要的数据(ID 和描述)在字典或一些小的 class 中,所以您不是 return正在处理所有数据。
有点不清楚你在这里处理的是什么。 "Model" 是一个含义丰富的术语,对各种各样的人来说可能意味着各种各样的事情。
如果 类、Fee
、Invoice
等是实体,也就是说它们直接与数据库相关 table,则对它们进行子类化出于视图的目的,就像您的开发人员所做的那样,是 100% 错误的。但是,如果它们也只是视图模型,那么将它们子类化可能有也可能没有优点。
如果它们是实体,那么您的开发人员是正确的,您不应该将整个实体 to/from 传递给视图。然而,解决方案是创建视图模型,在这种情况下,它只包含视图所需的属性。然后,您将从您的实体 to/from 这些视图模型映射数据。
关于使用 AutoMapper 进行映射的要点:您真的不应该使用 AutoMapper 将 映射到 一个实体。图书馆的开发者自己也说过很多。 AutoMapper 从未打算用于将数据映射回实体,如果您这样做,您将 运行 陷入 Entity Framework 的各种特殊问题。这些都可以解决(在我知道得更好之前,我个人已经做过很多次),但是您开始需要的代码量和您必须做的自定义事情开始抵消使用 AutoMapper 的所有好处。地方。我建议您只需手动将数据从您的视图模型映射回您的实体。
您的新开发人员的风格是由技术决定的(在本例中 Entity Framework)- 在 MHO 中,这并不总是有助于正确的设计。
我的方法是...
模型应该在逻辑上设计有接口——不知道它们是如何使用的
但会迎合未来的需求。
在不破坏良好的情况下,继承应尽可能浅
模型设计。
视图应该只通过控制器与 BS 交互。
数据服务应该只与业务服务交互。
每个演示文稿都应该有自己的 ViewModel,它应该尽可能扁平。
但是 ViewModel 可能有子 ViewModel。
我在我的数据库服务层中使用 DAPPER 并创建了一个代码生成器,它将
生成 DS 方法和存储过程。 DS 方法只会有相关的
数据库中 insert/update 字段的属性。
这使您的代码精简、快速且非常易于管理
当你离开团队而其他人必须接手时。
因此我们向团队添加了一名新程序员,他对如何在 MVC 中创建模型有一些想法,这与我们之前创建模型的方式不同。比方说,我们有一个系统,用户可以在其中提交文档请求,并且该系统中有一个页面,用户可以在该页面上计算完成该文档请求的费用。这个费用创建页面可以输入一些关于费用的数据以及相关的发票。用户可以添加发票行项目并使用它们来自动计算费用总额。在那种情况下,我们通常会创建如下模型。
public class Fee
{
public virtual Guid RequestID { get; set; }
public virtual Guid FeeID { get; set; }
public string FeeTitle { get; set; }
public decimal FeeAmount { get; set; }
public DateTime? DueDate { get; set; }
public decimal AmountPaid { get; set; }
public Invoice Invoice { get; set; }
public List<InvoiceLineItem> LineItems { get; set; }
}
public class Invoice
{
// Additional Invoice Data (To, From, etc)
}
public class InvoiceLineItem
{
public string LineItemTitle { get; set; }
public int Quantity { get; set; }
public decimal PricePerUnit { get; set; }
public decimal Subtotal { get; set; }
}
我们的新程序员认为这不是一个好的方法,因为不同的操作需要不同的数据。例如,当您创建费用时,您需要知道相应的请求 ID。但是,当您更新费用时,您只需要知道 FeeID。因此,当他创建他的模型时,他以这样一种方式创建它们,即存在多层继承,以努力控制在服务层中更新并呈现在视图上的数据。他的想法是,我们应该能够假设为交易传递的任何模型都应该使用其所有数据点,而不必根据情况猜测数据是什么。
对我来说,这给我们的模型增加了大量不必要的复杂性,并使得在其他模块上使用它们变得更加困难。下面的示例说明了这一点。
/// <summary>
/// This model is used to present data in a read fashion to the end user
/// </summary>
public class FeeViewModel : FeeModel_Create
{
public string FullRequestNumber { get; set; }
public decimal Balance { get; set; }
public List<String> States { get; set; }
public List<FeeAttachmentEditModel> Attachments { get; set; }
public List<PaymentViewModel> Payments { get; set; }
}
/// <summary>
/// This model adds a request id to the fee update model because we need to know which request this fee is associated with
/// </summary>
public class FeeModel_Create : FeeModel_Update
{
public Guid RequestID { get; set; }
}
/// <summary>
/// Represents the parameters required to update a fee
/// </summary>
public class FeeModel_Update
{
public virtual Guid FeeID { get; set; }
public decimal FeeAmount { get; set; }
public DateTime? DueDate { get; set; }
public string FeeTitle { get; set; }
public decimal AmountPaid { get; set; }
public List<MaterialList> MaterialTypes { get; set; }
public List<InvoiceLineItem_Adhoc> LineItems { get; set; }
public Invoice Invoice { get; set; }
public void InjectValuesIntoInvoiceModel(Invoice Invoice)
{
Invoice.Description = this.Invoice.Description;
Invoice.Terms = this.Invoice.Terms;
Invoice.To_Name = this.Invoice.To_Name;
Invoice.To_Address = this.Invoice.To_Address;
Invoice.To_Address2 = this.Invoice.To_Address2;
Invoice.To_City = this.Invoice.To_City;
Invoice.To_State = this.Invoice.To_State;
Invoice.To_Zip = this.Invoice.To_Zip;
Invoice.From_Name = this.Invoice.From_Name;
Invoice.From_Address = this.Invoice.From_Address;
Invoice.From_Address2 = this.Invoice.From_Address2;
Invoice.From_City = this.Invoice.From_City;
Invoice.From_State = this.Invoice.From_State;
Invoice.From_Zip = this.Invoice.From_Zip;
}
}
public class InvoiceLineItem_Adhoc
{
public string Type { get; set; }
public string EnteredBy { get; set; }
public decimal Quantity { get; set; }
public decimal UnitCost { get; set; }
public InvoiceLineItem ToLineItem(Guid InvoiceID)
{
var lineItem = new InvoiceLineItem();
StaticValueInjecter.InjectFrom(lineItem, this);
lineItem.InvoiceLineItemID = Guid.NewGuid();
lineItem.InvoiceID = InvoiceID;
lineItem.UserID = 1;
return lineItem;
}
}
public class PaymentViewModel
{
public Guid RequestID { get; set; }
public Guid FeeID { get; set; }
public string FullRequestNumber { get; set; }
public string FeeTitle { get; set; }
public virtual Guid PaymentID { get; set; }
public decimal PaymentAmount { get; set; }
public Nullable<System.DateTime> DatePaid { get; set; }
}
public class FeeAttachmentEditModel
{
public Guid RequestID { get; set; }
public Guid FeeID { get; set; }
public string FullRequestNumber { get; set; }
public string FeeTitle { get; set; }
public virtual System.Guid FeeAttachmentID { get; set; }
public System.Guid AttachmentTypeID { get; set; }
public string AttachmentName { get; set; }
public byte[] Data { get; set; }
public string Extension { get; set; }
public string mimeType { get; set; }
public string AttachmentBody { get; set; }
public HttpPostedFileBase FileUpload { get; set; }
public string FileName { get; set; }
public bool HadError = false;
}
我只是想在这里寻找关于在 MVC 中创建模型的最佳实践的答案。您是否应该创建单独的模型,无论是通过继承部分 类 还是其他方式来适应您正在进行创建、读取、更新或删除的操作。还是让一个视图模型转换为从视图中呈现的内容 on/passed 以及在访问数据时过滤掉来自视图模型的重要内容的逻辑更好?
我们采用的典型方法是拥有一个与视图紧密耦合且仅包含该信息的 ViewModel。 InputModels 也是如此,它们应该只包含将传入的属性。至于继承部分,我会远离这种方法。只需创建简单、扁平的 DTO 并从您的域模型映射它们。应该没有逻辑,所以 DRY 并不真正适用于您应用程序的这一层。
根据第一个答案,我们还没有看到如何在页面上使用视图模型。正如他所说,视图模型应该只包含满足显示视图的数据。您不应该只是盲目地将域模型中的每个字段复制到视图模型中。
我也不喜欢视图模型中有 HasError 标志。使用数据注释或继承自 IValidateableObject 对 POST 执行验证。
我也不认为您需要像在视图模型中那样注入值。如果您应该能够从 linq 查询或 web 服务请求直接投影到您的视图模型中。
最后一点是,对于页面上的下拉菜单,只有 return 您需要的数据(ID 和描述)在字典或一些小的 class 中,所以您不是 return正在处理所有数据。
有点不清楚你在这里处理的是什么。 "Model" 是一个含义丰富的术语,对各种各样的人来说可能意味着各种各样的事情。
如果 类、Fee
、Invoice
等是实体,也就是说它们直接与数据库相关 table,则对它们进行子类化出于视图的目的,就像您的开发人员所做的那样,是 100% 错误的。但是,如果它们也只是视图模型,那么将它们子类化可能有也可能没有优点。
如果它们是实体,那么您的开发人员是正确的,您不应该将整个实体 to/from 传递给视图。然而,解决方案是创建视图模型,在这种情况下,它只包含视图所需的属性。然后,您将从您的实体 to/from 这些视图模型映射数据。
关于使用 AutoMapper 进行映射的要点:您真的不应该使用 AutoMapper 将 映射到 一个实体。图书馆的开发者自己也说过很多。 AutoMapper 从未打算用于将数据映射回实体,如果您这样做,您将 运行 陷入 Entity Framework 的各种特殊问题。这些都可以解决(在我知道得更好之前,我个人已经做过很多次),但是您开始需要的代码量和您必须做的自定义事情开始抵消使用 AutoMapper 的所有好处。地方。我建议您只需手动将数据从您的视图模型映射回您的实体。
您的新开发人员的风格是由技术决定的(在本例中 Entity Framework)- 在 MHO 中,这并不总是有助于正确的设计。
我的方法是...
模型应该在逻辑上设计有接口——不知道它们是如何使用的 但会迎合未来的需求。 在不破坏良好的情况下,继承应尽可能浅 模型设计。
视图应该只通过控制器与 BS 交互。 数据服务应该只与业务服务交互。 每个演示文稿都应该有自己的 ViewModel,它应该尽可能扁平。 但是 ViewModel 可能有子 ViewModel。
我在我的数据库服务层中使用 DAPPER 并创建了一个代码生成器,它将 生成 DS 方法和存储过程。 DS 方法只会有相关的 数据库中 insert/update 字段的属性。 这使您的代码精简、快速且非常易于管理 当你离开团队而其他人必须接手时。