0

Trying to figure out the best place to store calculated properties related to a base entity class for use through out the application, namely in view models and DTOs.

For example, a base entity class of OrderDetail may have properties OrderedQuantity and UnitPrice. Where would I put the calculated property ExtenedPrice which is OrderedQuantity * UnitPrice.

I originally had it in a partial class definition of OrderDetail, which seemed to make sense, but I couldn't really use these new properties when creating view models.

Another thought was to create a new base view model class for each entity to contain these types of calculations, then have the view models inherit from them...but wasn't sure if a base class could use the fields of the derived class in it's calculations...this seemed backwards to me.

Anyway, before barking up the wrong tree, I thought I'd get some guidance from others.

UDPATE

Also not just for calculated properties, but also property annotations like Display, Required, and MaxLength.

2

Composition coupled with edtiro templates will give you the easiest code reuse possibilities.

Each view model that needs this information needs a reference to the shared view model.

public class AddEditOrderDetails
{
    [Display(Name = "Unit Price")]
    [Required]
    public decimal UnitPrice { get; set; }

    [Display(Name = "Quantity")]
    [Required]
    public int Quantity { get; set; }

    public decimal ExtendedPrice => UnitPrice * Quantity;
}

public class AddEditFoo
{
    public AddEditOrderDetails Details { get; set; }
}

public class AddEditBar
{
    public AddEditOrderDetails Details { get; set; }
}

Notice that the AddEditOrderDetails view model has the ExtendedPrice which multiples the quantity and unit price.

Then create a new partial view called Views/Shared/EditorTemplates/AddEditOrderDetails.cshtml that has the form fields (minus a field for ExtendedPrice, since this is a calculated property).

In each of the other pages:

@Html.EditorFor(model => model.Details)

Will render all the fields in the editor template, and run all associated validations.


Update: To avoid constantly mapping entities to the same view model, you can specify a common interface for your entities, and then pass the entities to a constructor on the view model that accepts the interface.

First, the define the interface to be the simplest you need it to be:

public interface IOrderLineItem
{
    public decimal UnitPrice { get; }
    public int Quantity { get; }
}

Then implement this interface in the entity classes:

public class Entity1 : IOrderLineItem
{
    ...
}


public class Entity2 : IOrderLineItem
{
    ...
}

Now add a few constructors to your view model to do the mapping work for you:

public class AddEditOrderDetails
{
    ...

    // For the MVC model binder or a new order line item
    public AddEditOrderDetails()
    {
    }

    // For editing an existing order line item
    public AddEditOrderDetails(IOrderLineItem orderLineItem)
    {
        UnitPrice = orderLineItem.UnitPrice;
        Quantity = orderLineItem.Quantity;
    }
}

Now mapping from entity to view model is easy:

var model = new MainPageViewModel1()
{
    Details = new AddEditOrderDetails(entity1)
};

var model = new MainPageViewModel1()
{
    Details = new AddEditOrderDetails(entity2)
};

var model = new MainpageViewModel()
{
    Items = mainEntity.SubEntities1
        .Select(s => new AddEditOrderDetails(s))
        .ToList()
}
  • I like this, but you have to manually set the UnitPrice and Quantity. Could you convert AddEditOrderDetails to an interface IAddEditOrderDetails and then have AddEditOrderDetails class which just has the calculated property and have your view models inherit from it? i.e. AddEditFoo: AddEditOrderDetails, IAddEditOrderDetails. And your editor template can accept type IAddEditOrderDetails @Html.EditorFor(model => model) – Chad Richardson Jun 18 at 13:48
  • 1
    You won't be able to use an interface here, because the MVC model binder won't be able to infer the concrete type when posting the form back to the server. You can define an interface for the entities that have the price and quantity, and then add a constructor to the view model that accepts the interface as an argument. I can post an update to my answer. I've done this before and it works pretty well. – Greg Burghardt Jun 18 at 13:50
  • 1
    @ChadRichardson: I updated my answer to address your comment. – Greg Burghardt Jun 18 at 14:00
  • Thanks Greg. What are your thoughts, if these calculated properties don't apply to other entity types, of adding them via a partial class definition to the base entity type class? To me, it keeps the logic closest to the entity definition itself, but don't know if there is any reason not to do this. – Chad Richardson Jun 18 at 15:39
  • 1
    @ChadRichardson: If they calculated values don't apply to all types, then you really should use different classes. It is all to easy to abuse inheritance in these kinds of cases. Generally speaking, if the use case is different, don't be afraid to create view models that look similar, or are outright copies of each other. – Greg Burghardt Jun 18 at 16:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.