namespace After; /// /// Hand-written mappers out of the plain domain (no AutoMapper — the mapping /// code is part of the illustration). Each public method maps in exactly one /// direction and never mutates its input. Two families, two destinations: /// /// / — transport to the /// WebApp; /// / — the data-only /// records the persistence framework stores. /// /// /// The Client.Orders / Order.Client back-reference cycle is /// resolved with a reference-identity cache: the same instance maps to the /// same DTO (and back), so the graph terminates and stays consistent. The /// persistence mapping needs no cache: records reference each other by id, /// not by object, so the graph is flattened and cannot cycle. /// public static class Mappers { /// Map a (with its orders) to a . public static ClientDto ToClientDto(Client client) { ArgumentNullException.ThrowIfNull(client); return MapClient(client, new Dictionary()); } /// Map an (with its client back-reference) to an . public static OrderDto ToOrderDto(Order order) { ArgumentNullException.ThrowIfNull(order); return MapOrder(order, new Dictionary()); } /// Map a (with its orders) back to a . public static Client FromClientDto(ClientDto dto) { ArgumentNullException.ThrowIfNull(dto); return UnmapClient(dto, new Dictionary()); } /// Map an (with its client back-reference) back to an . public static Order FromOrderDto(OrderDto dto) { ArgumentNullException.ThrowIfNull(dto); return UnmapOrder(dto, new Dictionary()); } // ------------------------------------------------------------------ // Domain -> DTO // ------------------------------------------------------------------ private static ClientDto MapClient(Client client, Dictionary seen) { if (seen.TryGetValue(client, out var cached)) return (ClientDto)cached; var dto = new ClientDto { Id = client.Id, Name = client.Name }; seen[client] = dto; // cache first: breaks the cycle dto.Orders = client.Orders .Select(o => MapOrder(o, seen)) .ToList(); // fill second, through the cache return dto; } private static OrderDto MapOrder(Order order, Dictionary seen) { if (seen.TryGetValue(order, out var cached)) return (OrderDto)cached; var dto = new OrderDto { Id = order.Id, Description = order.Description, TotalInCents = order.TotalInCents, ClientDto = order.Client is null ? null : MapClient(order.Client, seen), }; seen[order] = dto; return dto; } // ------------------------------------------------------------------ // DTO -> domain // ------------------------------------------------------------------ private static Client UnmapClient(ClientDto dto, Dictionary seen) { if (seen.TryGetValue(dto, out var cached)) return (Client)cached; var client = new Client { Id = dto.Id, Name = dto.Name }; seen[dto] = client; // cache first: breaks the cycle foreach (var orderDto in dto.Orders) client.Orders.Add(UnmapOrder(orderDto, seen)); // fix-up wires order.Client return client; } private static Order UnmapOrder(OrderDto dto, Dictionary seen) { if (seen.TryGetValue(dto, out var cached)) return (Order)cached; var order = new Order { Id = dto.Id, Description = dto.Description, TotalInCents = dto.TotalInCents, Client = dto.ClientDto is null ? null : UnmapClient(dto.ClientDto, seen), }; seen[dto] = order; return order; } // ------------------------------------------------------------------ // Domain -> persistence (data-only records; graph flattened to ids) // ------------------------------------------------------------------ /// Map a to its persistence record . public static ClientData ToClientData(Client client) { ArgumentNullException.ThrowIfNull(client); return new ClientData { Id = client.Id, Name = client.Name }; } /// /// Map an to its persistence record : /// the object graph is flattened — becomes the /// foreign key (empty when the order has /// no client, or when that client has not been saved yet). /// public static OrderData ToOrderData(Order order) { ArgumentNullException.ThrowIfNull(order); return new OrderData { Id = order.Id, Description = order.Description, TotalInCents = order.TotalInCents, ClientId = order.Client?.Id ?? Guid.Empty, }; } }