简体   繁体   English

在 MVC (CRUD) 中创建模型的最佳实践

[英]Best practice for creating models in MVC (CRUD)

So we have added a new programmer to the team and he has some thoughts on how models should be created within MVC that is different from how we have created models previously.所以我们在团队中增加了一名新程序员,他对如何在 MVC 中创建模型有一些想法,这与我们之前创建模型的方式不同。 Lets say for example we have a system in which users submit requests for documents and that there is a page in that systems where users calculate a fee for fulfilling that document request.例如,我们有一个系统,用户可以在其中提交文件请求,并且该系统中有一个页面,用户可以在其中计算完成该文件请求的费用。 This fee creation page would have the abiltiy to type in some data about the fee and also the associated invoice.此费用创建页面可以输入有关费用的一些数据以及相关发票。 Users could add invoice line items and use those to auto calculate a fee total.用户可以添加发票行项目并使用它们来自动计算费用总额。 In that scenario we would normally create models like the below.在这种情况下,我们通常会创建如下所示的模型。

    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; }

}

Our new programmer believes that this is not a good approach because there are different data needs for different operations.我们的新程序员认为这不是一个好方法,因为不同的操作有不同的数据需求。 For instance when you create a fee you will need to know the corresponding request ID.例如,当您创建费用时,您需要知道相应的请求 ID。 However when you update a fee you will only need to know the FeeID.但是,当您更新费用时,您只需要知道费用 ID。 Therefore when he has created his models he has created them in such a fashion as that there are multiple layers of inheritance in an effort to control the data that is updated in the service layer and presented on the view.因此,当他创建他的模型时,他以这样一种方式创建它们,即存在多个继承层以努力控制在服务层中更新并呈现在视图上的数据。 His thought is that we should be able to assume that any model passed in for a transaction should have all its data points used and not have to guess on what the data depending on circumstance.他的想法是,我们应该能够假设为交易传入的任何模型都应该使用其所有数据点,而不必根据情况猜测数据是什么。

To me this adds a ton of needless complexity to our models and makes it far more difficult to work with them on other modules.对我来说,这给我们的模型增加了大量不必要的复杂性,并使在其他模块上使用它们变得更加困难。 Below is a sample which illustrates this.下面是一个示例,说明了这一点。

 /// <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;
}

I'm just looking for an answer here on what the best practices for creating models are in MVC.我只是在这里寻找关于在 MVC 中创建模型的最佳实践的答案。 Should you create separate models whether through inheritance partial classes or other means to accommodate which operation you are conducting creating, reading, updating, or deleting.您是否应该通过继承部分类或其他方式创建单独的模型,以适应您正在执行的创建、读取、更新或删除操作。 Or is it better to have one viewmodel that translates to what is presented on/passed from the view and the logic to filter out what is important that's coming from the view model when accessing the data?或者最好有一个视图模型来转换为视图呈现/传递的内容,以及在访问数据时过滤掉来自视图模型的重要内容的逻辑?

The typical approach that we take is to have a ViewModel that is tightly coupled to a view and only contains that information.我们采用的典型方法是拥有一个与视图紧密耦合且仅包含该信息的 ViewModel。 The same goes for InputModels, they should only contain the properties that will be passed in. As for the inheritance piece, I would stay far far away from that approach. InputModels 也是如此,它们应该只包含将传入的属性。至于继承部分,我将远离这种方法。 Just create simple, flat DTOs and map them from your domain model.只需创建简单、扁平的 DTO 并从您的域模型中映射它们。 There should be no logic so DRY doesn't really apply to this layer of your app.应该没有逻辑,因此 DRY 并不真正适用于您应用程序的这一层。

per the first answer, we haven't seen how the viewmodel will be used on a page.根据第一个答案,我们还没有看到如何在页面上使用视图模型。 Like he said, the viewmodel should only contain data that will satisfy displaying the view.就像他说的那样,视图模型应该只包含满足显示视图的数据。 you shouldn't just be blindly copying every field from your domain model into the viewmodel.您不应该盲目地将域模型中的每个字段复制到视图模型中。

I also don't like that there's a HasError flag in the view model(s).我也不喜欢视图模型中有一个 HasError 标志。 Use data annotations or inherit from IValidateableObject to perform validation on your POST's.使用数据注释或从 IValidateableObject 继承来对您的 POST 执行验证。

I also don't think you need to be injecting values like you are into viewmodel.我也不认为您需要像在视图模型中那样注入值。 if you should be able to projet directly into your viewmodel from a linq query or webservice request.如果您应该能够从 linq 查询或网络服务请求直接投影到您的视图模型中。

and the last piece is that for dropdowns on the page, only return the data you need (an Id and description) in a dictionary or some small class so you aren't returning all the data.最后一部分是对于页面上的下拉菜单,只在字典或某个小类中返回您需要的数据(ID 和描述),这样您就不会返回所有数据。

It's a little unclear what you're dealing with here.有点不清楚你在这里处理什么。 "Model" is a loaded term and can mean all sorts of things to all sorts of people. “模型”是一个负载术语,可以对各种各样的人表示各种各样的东西。

If the classes, Fee , Invoice , etc., are entities, which is to say they related directly to a database table, then subclassing them for the purposes of the view, like your developer is doing, is 100% wrong.如果类FeeInvoice等是实体,也就是说它们直接与数据库表相关,那么为了视图的目的对它们进行子类化,就像您的开发人员所做的那样,是 100% 错误的。 However, if they are merely view models as well, then there may or may not be merit in subclassing them.但是,如果它们也仅仅是视图模型,那么对它们进行子类化可能有也可能没有优点。

If they are entities, then your developer is right in the sense that you should not be passing the whole entities to/from the view.如果它们是实体,那么您的开发人员是正确的,您不应该将整个实体传入/传出视图。 However, the solution is to create view models, in this case, that contain just the properties necessary for the view.然而,解决方案是创建视图模型,在这种情况下,只包含视图所需的属性。 You would then map the data from your entities to/from these view models.然后,您可以将实体中的数据映射到这些视图模型或从这些视图模型映射。

Minor point regarding using AutoMapper for the mapping: you really shouldn't use AutoMapper ever to map to an entity.关于使用 AutoMapper 进行映射的小问题:您真的不应该使用 AutoMapper 来映射实体。 The developer of the library has said as much himself.库的开发者自己也说了这么多。 AutoMapper was never intended to be used to map data back onto an entity, and you'll run into all sorts of idiosyncratic issues with Entity Framework if you do so. AutoMapper 从未打算用于将数据映射回实体,如果这样做,您将遇到实体框架的各种特殊问题。 These can all be worked around (I've personally done this many times before I knew better), but the amount of code you begin to require and custom things you have to do begin to negate all the benefits of working with AutoMapper in the first place.这些都可以解决(在我知道更好之前,我个人已经做过很多次),但是您开始需要的代码量和您必须做的自定义事情开始否定使用 AutoMapper 的所有好处地方。 I'd recommend simply manually mapping the data from your view model back onto your entity.我建议简单地手动将数据从您的视图模型映射回您的实体。

Your new developer's style is being dictated by the technology (in this case Entity Framework) - which in MHO does not always lend to proper design.您的新开发人员的风格由技术(在本例中为实体框架)决定 - 在 MHO 中并不总是适合正确的设计。

My approach is ...我的做法是...

Models should be logically designed with interfaces - agnostic of how they are used but will cater to future requirements.模型应该用接口进行逻辑设计——不知道它们的使用方式,但会满足未来的需求。 The inheritance should be kept as shallow as possible without breaking a good model design.在不破坏良好模型设计的情况下,继承应该尽可能地浅。

Views should only interact with the BS via controllers.视图应该只通过控制器与 BS 交互。 Data Services should only interact with Business services.数据服务应该只与业务服务交互。 Each presentation should have it's own ViewModel which should be as flat as possible.每个演示文稿都应该有它自己的 ViewModel,它应该尽可能地平坦。 However a ViewModel may have child ViewModels.然而,一个 ViewModel 可能有子 ViewModel。

I use DAPPER for in my database services layer and created a code generator that will generate the DS methods and stored procs.我在我的数据库服务层中使用 DAPPER 并创建了一个代码生成器,它将生成 DS 方法和存储过程。 The DS method would only have the relevant properties to insert/update fields in a DB. DS 方法将仅具有在数据库中插入/更新字段的相关属性。 This makes your code lean, fast and very easy to manage when you leave the team and someone else has to take over.当您离开团队而其他人必须接手时,这将使您的代码精简、快速且非常易于管理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM