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”