Skip to content

DTO data copying

Jon Smith edited this page Jan 13, 2015 · 11 revisions

DTO-to-data and data-to-DTO copying

GenericServices uses AutoMapper's projection method to do the database-to-DTO or DTO-to-database copying of data. AutoMapper uses a convention-based mapping that tries to link up by name and type.

AutoMapper is really sophisticated with many options which you can read about in the AutoMapper Wiki. However to save you time I will list the ones that are most useful:

  • Match by name/Type. If you have an int property in your databases class called CustomerID and another int property called CustomerId in your DTO then it will copy the data (see note below for exception).
  • Flattening. If a database class called Product has a relationship such that it can access a property by writing ProductCategory.Name then adding the same name without the full stop, i.e. ProductCategoryName, with the right type to the DTO will access that property.
  • Aggregation. AutoMapper allows you some simple, but powerful aggregations, with the most useful being Count. If I have a property called Orders, which is a collection of all orders placed by a customer then having a property called OrdersCount in the DTO will return the number of orders. (see note 2 below)

NOTES:

  1. When coping data from a DTO to a database class if a DTO property has the GenericServices attribute called [DoNotCopyBackToDatabase] that property will NOT be copied back to the database class. This is a very important attribute as allows the developer to stop certain properties from being updated by the user, e.g. MySalary. This provides a similar capability to the [Bind()] attribute on MVC action parameters.
  2. When using AutoMapper's Aggregation I have found that xxxCount(), xxxAny(), xxxMax() etc. work, but for some reason xxxSum() does not.

Using AutoMapper for calculated properties

Calculated properties, like public string FullName { get { return FirstName + " " + LastName ; } } are normally done by using the [Computed] attribute and the DelegateDecompiler option (see Calculated Properties). However there are reasons why you may not want to, or be able to use the DelegateDecompiler option. They are:

  1. There are a few cases which the DelegateDecompiler cannot handle, such as parameters that need a external variable, e.g. a comparison with some user-provided variable.
  2. You have decided that you do not want to use the DelegateDecompiler as it may interfere with your business logic, i.e. you may need to add the .DecompileAsync() method to EF accesses that use that a [Computed] parameter. Not very likely, but you might have some reason you don't want to use the DelegateDecompiler.

In these cases AutoMapper's .ForMember mapping can to be used. The EfGenericDto/EfGenericDtoAsync classes provides a property called AddedDatabaseToDtoMapping which can be overridden to add .ForMember LINQ statements that will fill in given property. Below is an example which sets the property IsOnSale to true using a local variable called _today which has been set to DateTime.Now when the DTO was created.

protected override Action<IMappingExpression<Product, ListProductDto>> AddedDatabaseToDtoMapping
{
    get
    {
        return m => m.ForMember(d => d.IsOnSale,
            opt => opt.MapFrom(c => 
                       c.SellStartDate < _today &&
                       _today <= (c.SellEndDate ?? _today)));
    }
}

In this case there is only one .ForMember method, but they can be chained, e.g.

protected override Action<IMappingExpression<Post, PostSpecialMappingDto>> 
    AddedDatabaseToDtoMapping
{
    get { return 
          m => m.ForMember(p => p.Bigger, 
                    opt => opt.MapFrom(x => x.SomeProp > localVariable))
                .ForMember(p => p.Smaller, 
                    opt => opt.MapFrom(x => x.SomeProp < anotherLocalVariable)); }
}