Mapowania między DTO a obiektami biznesowymi

Klasycznym sposobem mapowania DTO na obiekt biznesowy jest użycie wzorca projektowego adapter. Przykładowo aby zmapować Order do OrderDto możemy napisać następującą klasę:

class OrderDtoAdapter
{
   private Order m_Order = null;
   public OrderDtoAdapter(Order order)
   {
       m_Order = order;
   }
   public void Initialize(Order orderDto)
   {
       orderDto.CreationDate = m_Order.CreationDate;
       orderDto.Client = m_Order.Client;
       orderDto.Id = m_Order.Id;
   }
}

Rozwiązanie całkowicie poprawne jednak bardzo czasochłonne. Napisanie osobnej klasy dla każdej pary DTO-obiekt biznesowy wymaga sporo czasu. Na szczęście istnieje biblioteka AutoMapper, którą można wykorzystać. Generalnie przepisuje ona każdą właściwość z jednej klasy do drugiej. Bibliotekę możecie ściągnąć  z Codeplex’a.

Spróbujmy zmapować jakiś obiekt. Załóżmy, że obiekt biznesowy wygląda następująco:

public class Order : BaseDomainModel.BusinessObjects.DomainModel, EnterpriseStateMachine.Orders.IOrderService
{
   private BaseDomainModel.Workflow.StateMachineStarter m_StateMachineStarter = null;
   
   public Order()
   {
       OrderItems = new List<OrderItem>();
       CreationDate = DateTime.Now;
       m_StateMachineStarter = new BaseDomainModel.Workflow.StateMachineStarter(this);
   }
   virtual public DateTime CreationDate { get; set; }
   virtual public PaymentType PaymentType { get; set; }
   virtual public IList<OrderItem> OrderItems { get; set; }
   virtual public string Status { get; set; }
   virtual public CrmDomainModel.Clients.Client Client { get; set; }

   
   private Dictionary<string, object> GetStateMachinePars()
   {
       Dictionary<string, object> parameters = new Dictionary<string, object>() { { "OrderId", Id }, { "ClientId", Client.Id } };
       parameters.Add("OnDeliveryPayment", PaymentType == PaymentType.OnDelivery);
       return parameters;
   }

   virtual public void Submit()
   {
       m_StateMachineStarter.CallWorkflowSync(Status, GetStateMachinePars(), ref OrderSubmitted, (instanceId) => OrderSubmit(null, new OrderEventArgs(instanceId)));
   }
   virtual public void Approve()
   {
       m_StateMachineStarter.CallWorkflowSync(Status, GetStateMachinePars(), ref OrderApproved, (instanceId) => OrderApprove(null, new ApproveEventArgs(instanceId)));

   }   
   virtual public void SetOrderAsSent()
   {
       m_StateMachineStarter.CallWorkflowSync(Status, GetStateMachinePars(), ref OrderPackageSent, (x) => OrderPackageSend(null, new OrderEventArgs(x)));
   }   
   virtual public void SetOrderAsReceived()
   {
       m_StateMachineStarter.CallWorkflowSync(Status, GetStateMachinePars(), ref OrderPackageReceived, (x) => OrderPacakgeReceive(null, new OrderEventArgs(x)));
   }   
   virtual public void SetOrderAsPacked()
   {
       m_StateMachineStarter.CallWorkflowSync(Status, GetStateMachinePars(), ref OrderPacked, (x) => OrderPack(null, new OrderEventArgs(x)));
   }
   virtual public void SetOrderAsPaid()
   {
       m_StateMachineStarter.CallWorkflowSync(Status, GetStateMachinePars(), ref OrderPaid, (x) => OrderPay(null, new OrderEventArgs(x)));
   }   
   virtual public void Disapprove()
   {
       m_StateMachineStarter.CallWorkflowSync(Status, GetStateMachinePars(), ref OrderDisapproved, (x) => OrderDisapprove(null, new DisapproveEventArgs(x)));
   }
   
   void EnterpriseStateMachine.Orders.IOrderService.AddOrderToDB(string defaultStatus)
   {
       this.Status = defaultStatus;
       BaseDomainModel.BusinessObjects.Repositories.IDMRepository repository = null;

       BaseDomainModel.BusinessObjects.Repositories.IRepositoryLocalizer localizer = BaseDomainModel.BusinessObjects.Repositories.RepositoryLocalizerPlugin.Instance.GetLocalizer();
       repository = localizer.GetForBusinessObject(typeof(Order));

       repository.Create<Order>(this);
   }
   void EnterpriseStateMachine.Orders.IOrderService.UpdateOrderStatus(string status)
   {
       BaseDomainModel.BusinessObjects.Repositories.IDMRepository repository = null;
       repository =
           BaseDomainModel.BusinessObjects.Repositories.RepositoryLocalizerPlugin.Instance.GetLocalizer().
               GetForBusinessObject(this.GetType());
       this.Status = status;
       repository.Update<Order>(this);
   }
   virtual public event EventHandler<OrderEventArgs> OrderSubmit;
   virtual public event EventHandler OrderSubmitted;


   virtual public event EventHandler<ApproveEventArgs> OrderApprove;
   virtual public event EventHandler OrderApproved;

   virtual public event EventHandler<DisapproveEventArgs> OrderDisapprove;
   virtual public event EventHandler OrderDisapproved;

   virtual public event EventHandler<OrderEventArgs> OrderPack;
   virtual public event EventHandler OrderPacked;

   virtual public event EventHandler<OrderEventArgs> OrderPackageSend;
   virtual public event EventHandler OrderPackageSent;

   virtual public event EventHandler<OrderEventArgs> OrderPacakgeReceive;
   virtual public event EventHandler OrderPackageReceived;

   virtual public event EventHandler<OrderEventArgs> OrderPay;
   virtual public event EventHandler OrderPaid;
   
   void EnterpriseStateMachine.Orders.IOrderService.OnDisapproved()
   {
       this.OrderDisapproved.RaiseEvent(this, null);
   }   
   void EnterpriseStateMachine.Orders.IOrderService.OnPaid()
   {
       this.OrderPaid.RaiseEvent(this, null);
   }
   void EnterpriseStateMachine.Orders.IOrderService.OnSubmitted()
   {
       this.OrderSubmitted.RaiseEvent(this, null);
   }   
   void EnterpriseStateMachine.Orders.IOrderService.OnApproved()
   {
       this.OrderApproved.RaiseEvent(this, null);
   }

   void EnterpriseStateMachine.Orders.IOrderService.OnPackageSent()
   {
       this.OrderPackageSent.RaiseEvent(this, null);
   }
   void EnterpriseStateMachine.Orders.IOrderService.OnPackageReceived()
   {
       OrderPackageReceived.RaiseEvent(this, null);
   }
   void EnterpriseStateMachine.Orders.IOrderService.OnPacked()
   {
       OrderPacked.RaiseEvent(this, null);
   }
}

Co dokładnie powyższa klasa  zawiera nie ma większego znaczenia ;). Ważne jest tylko to, że występują w niej właściwości oraz metody (logika). Odpowiadający DTO zawiera oczywiście wyłącznie właściwości:

[DataContract()]
public class OrderDto:DtoBase.DtoBase
{
   public OrderDto()
   {
       OrderItems = new List<OrderItemDto>();
   }
   [DataMember]
   public string Status { get; set; }
   
   [DataMember]
   public DateTime CreationDate { get; set; }
   
   [DataMember]
   public List<OrderItemDto> OrderItems { get; set; }
   
   [DataMember]
   public CrmDto.Client.ClientDto Client { get; set; }
   
   [DataMember]
   public PaymentType PaymentType { get; set; }
}

Chcemy zatem zmapować Order do OrderDto. Innymi słowy przepisać wartości wszystkich właściwości Order do OrderDto. W AutoMapper wystarczy najpierw stworzyć takie mapowanie za pomocą:

AutoMapper.Mapper.CreateMap<Order, OrderDto>();

Następnie wystarczy tylko wywołać:

OrderDto ordersDto = AutoMapper.Mapper.Map<Order, OrderDto>(order);

W analogiczny sposób można mapować całe tablice:

OrderDto[] ordersDto = AutoMapper.Mapper.Map<Order[], OrderDto[]>(orders);

Można również wywoływać jakąś metodę po lub przed mapowaniem (callback):

private void Init()
{
    Mapper.CreateMap<OrderDto, Order>().AfterMap(AfterOrderMap);
}
private void AfterOrderMap(OrderDto orderDto, Order order)
{  
  foreach (OrderItem item in order.OrderItems)
      item.Order = order;
      
  int paymentType=(int)orderDto.PaymentType;
  order.PaymentType = (EnterpriseDomainModel.Orders.PaymentType)paymentType;
}

Jeśli w trakcie mapowania chcemy pominąć jakieś właściwości, ponieważ są one np. generowane dynamiczne można to zrobić za pomocą metody Ignore:

Mapper.CreateMap<OrderDto, Order>().AfterMap(AfterOrderMap).ForMember("wlasciwosc", (x) => x.Ignore());

Interfejs biblioteki jest przyjazny i naprawdę łatwo zrealizować wszystko to co się chce. Szczegółowe informacje znajdziecie na stronie biblioteki. Dodam tylko, że istnieje również możliwość pisania własnych konwerterów.

2 thoughts on “Mapowania między DTO a obiektami biznesowymi”

Leave a Reply

Your email address will not be published.