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,
};
}
}