Search code examples
asp.net-web-api2repository-patterndton-tier-architecture

Where to map DTOs to Entites for Create/Add method in repository


I have a Web Api Application where I have EF DB first entities and DTO classes. So this is my generic repository interface -

public interface IRepository<TEntity> where TEntity:class
    {
        IQueryable<TEntity> GetAll();
        void Create(TEntity Entity);

        TEntity GetById(int id);
        void Update(TEntity Entity);
        void Remove(int id);
    }

Here is a sample of the GetAll method implementation for one of the services -

public class OrdersRepository : IRepository<SalesOrderHeader>
    {
        private AdventureWorksEntities db = new AdventureWorksEntities();
 public IQueryable<SalesOrderHeader> GetAll()
        {
            return db.SalesOrderHeaders;
        
        }

And here is my service or apicontroller calling the method along with additional mapping -

public IQueryable<Orders> GetSalesOrderHeaders()
        {
            **var Orders = orderRepo.GetAll();**
            var OrderDetails = orderDetailRepo.GetAll();
            return (from so in Orders
                    select new Orders()
                    {
                        SalesOrderID = so.SalesOrderID,
                        SalesOrderNumber = so.SalesOrderNumber,
                        ShipDate = so.ShipDate.ToString(),
                        Customer = customerRepo.GetById(so.CustomerID),
                        OrderItems = (from sod in OrderDetails
                                      select new OrderItems()
                                      {
                                          SalesOrderId = sod.SalesOrderID,
                                          ProductID = sod.ProductID,
                                          Quantity = sod.OrderQty,
                                          UnitPrice = sod.UnitPrice
                                      }).Where(a => a.SalesOrderId == so.SalesOrderID).ToList()
                    });
        }

As seen here the mapping is being done here in the apicontroller class. Similarly for Create/Add method of repository, where will this happen? If in the apicontroller, does that mean I need access to Entity in my apicontroller? if in the Repository, then i will have to map the DTO to Entity in my repository. Both seem dubious. Here is my DTO class -

public class Orders
    {
        public int SalesOrderID { get; set; }
        public string SalesOrderNumber { get; set; }

        public string ShipDate { get; set; }
        public CustomerDTO Customer { get; set; }
      
        public List<OrderItems> OrderItems { get; set; }

    }

The entity class is called SalesOrderHeaders and has a lot more fields.


Solution

  • Neither. Like you, I faced this dilemma with my code and decided that implementing object mapping in aither the controller or the repository will only add innecesary complexity and will end up violating the DRY (Don't Repeat Yourself) principle, and since using an ORM was overkill for the size of the project, I end up creating a set of extension methods that allowed me do the job in a clean and reusable way.

    Here is an example, though incompleted, that may help you build your own:

    // File: ModelExtensions.Orders.cs
    public static partial class ModelExtensions
    {
       public static IEnumerable<OrdersDto> MapToDto(this IEnumerable<Orders> list)
       {
          return list.select(item => item.MapToDto());
       } 
    
       public static OrdersDto MapToDto(this Orders order)
       {
          var dto = new OrdersDto() {
             SalesOrderID = order.SalesOrderID,
             SalesOrderNumber = order.SalesOrderNumber,
             ShipDate = order.ShipDate.ToString()
          }
       }
    
       public static IEnumerable<OrdersDetailsDto> MapToDto(this IEnumerable<OrderDetails> list)
       {
          return list.select(item => item.MapToDto());
       } 
    
       public static OrderDetailsDto MapToDto(this OrderDetails orderDetails)
       {
          var dto = new OrderDetailsDto() {
             SalesOrderId = orderDetails.SalesOrderID,
             ProductID = orderDetails.ProductID,
             Quantity = orderDetails.OrderQty,
             UnitPrice = orderDetails.UnitPrice
          }
       }
    }
    

    You can also create similar mapping functions to map from DTOs to Entities.

    To use this functions, just include a reference to the namespace where the extension methods are defined, et voila, you have all your mappings at hand.

    Basically, the transformation would occur in the API method, since there is where the exchange (DTO/Entity or Entity/DTO) is made.

    Using this method, you could use the mapping functions wherever you need it without the need to repeat (DRY principle) the logic in different places.

    Sample usage:

    public IQueryable<Orders> GetSalesOrderHeaders()
    {
        var orders = orderRepo.GetAll().MapToDto();
        var orderDetails = orderDetailRepo.GetAll();
        foreach (var ord in orders)
        {
           ord.OrderItems = orderDetails.Where(a => a.SalesOrderId == ord.SalesOrderID).MapToDto();
        }
        return orders.ToList();
    }