Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Translating Entity Framework model navigation properties into DTOs

I’m currently working on an n-tier web project. After researching into Data Transfer Objects and their benefits we decided to give this pattern a go. Our ASP.NET MVC website does not have direct access to the EF DbContext but instead will use DTOs to send and receive entity data. There will be a service/mapping layer that will convert between DTOs and entity models.

My question is, what is the best way to translate entity model navigation properties into its DTO?

Below is an example of a entity model and its DTO from the project:

Entity Model:

public class Payment
{
    public int ID { get; set; }
    public DateTime? PaidOn { get; set; }
    public decimal Amount { get; set; }
    public string Reference { get; set; }

    //Navigation Properties
    public virtual PaymentMechanism PaymentMechanism { get; set; }
    public virtual ICollection<Order> Orders { get; set; }
}

DTO:

public class PaymentDto
{
    public int ID { get; set; }
    public DateTime? PaidOn { get; set; }
    public decimal Amount { get; set; }
    public string Reference { get; set; }

    //--------Navigation Properties - Object Ids--------
    public int PaymentMechanismId { get; set; }
    public ICollection<int> OrderIds { get; set; }
}

As can be seen they are very similar except for the navigation properties. I have changed them to hold integer Ids (of the entities) instead of the entity models. Therefore if the navigation property entities need to be obtained, their Id’s can passed into a service/mapping layer function which will retrieve the entities from then database, map them to DTOs and return the collection. Is this an acceptable way of doing things?

I am new to this area so some of my terminology might not be totally correct but hopefully you’ll understand what I'm getting at. If you need me to clarify or provide additional detail on anything, please let me know.

like image 240
Chris White Avatar asked May 16 '13 11:05

Chris White


2 Answers

You can load the DTOs using a projection:

var paymentDtos = context.Payments
    .Where(p => p.Amount >= 1000m) // just an example filter
    .Select(p => new PaymentDto
    {
        ID = p.ID,
        PaidOn = p.PaidOn,
        Amount = p.Amount,
        Reference = p.Reference,
        PaymentMechanismId = p.PaymentMechanism.ID,
        OrderIds = p.Orders.Select(o => o.ID)
    })
    .ToList();

You have to declare the OrderIds in the dto as IEnumerable<int> though, not as ICollection<int> to make this compile.

I'm not sure if this key collection is really useful. If you want to load the orders later you could do it in a separate service method just based on the ID of the Payment, like so:

public IEnumerable<OrderDto> GetPaymentOrders(int paymentID)
{
    return context.Payments
        .Where(p => p.ID == paymentID)
        .Select(p => p.Orders.Select(o => new OrderDto
        {
            ID = o.ID,
            //etc. mapping of more Order properties
        }))
        .SingleOrDefault();
}
like image 85
Slauma Avatar answered Nov 19 '22 00:11

Slauma


I'm usually using Automapper for this kind of scenario. I would create a Dto class form my main entity and also Dto's for my navigation property entities, then let Automapper do the mapping automatically, without having to write the mapping code manually.

public class PaymentDto
{
    public int ID { get; set; }
    public DateTime? PaidOn { get; set; }
    public decimal Amount { get; set; }
    public string Reference { get; set; }

    //Navigation Properties
    public virtual PaymentMechanismDto PaymentMechanism { get; set; }
    public virtual ICollection<OrderDto> Orders { get; set; }
}

public class PaymentMechanismDto
{
//properties
}

public class OrderDto
{
//properties
}


public class MappingProfile : Profile
{
        public MappingProfile()
        {
            Mapper.CreateMap< Payment, PaymentDto >();
            Mapper.CreateMap< PaymentMechanism, PaymentMechanismDto >();
            Mapper.CreateMap< Order, OrderDto >();
        }
}
like image 2
Marko Pavic Avatar answered Nov 19 '22 01:11

Marko Pavic