141 lines
5.5 KiB
C#
141 lines
5.5 KiB
C#
namespace After;
|
|
|
|
/// <summary>
|
|
/// 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:
|
|
/// <list type="bullet">
|
|
/// <item><see cref="ClientDto"/>/<see cref="OrderDto"/> — transport to the
|
|
/// WebApp;</item>
|
|
/// <item><see cref="ClientData"/>/<see cref="OrderData"/> — the data-only
|
|
/// records the persistence framework stores.</item>
|
|
/// </list>
|
|
///
|
|
/// The <c>Client.Orders</c> / <c>Order.Client</c> 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.
|
|
/// </summary>
|
|
public static class Mappers
|
|
{
|
|
/// <summary>Map a <see cref="Client"/> (with its orders) to a <see cref="ClientDto"/>.</summary>
|
|
public static ClientDto ToClientDto(Client client)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(client);
|
|
return MapClient(client, new Dictionary<object, object>());
|
|
}
|
|
|
|
/// <summary>Map an <see cref="Order"/> (with its client back-reference) to an <see cref="OrderDto"/>.</summary>
|
|
public static OrderDto ToOrderDto(Order order)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(order);
|
|
return MapOrder(order, new Dictionary<object, object>());
|
|
}
|
|
|
|
/// <summary>Map a <see cref="ClientDto"/> (with its orders) back to a <see cref="Client"/>.</summary>
|
|
public static Client FromClientDto(ClientDto dto)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(dto);
|
|
return UnmapClient(dto, new Dictionary<object, object>());
|
|
}
|
|
|
|
/// <summary>Map an <see cref="OrderDto"/> (with its client back-reference) back to an <see cref="Order"/>.</summary>
|
|
public static Order FromOrderDto(OrderDto dto)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(dto);
|
|
return UnmapOrder(dto, new Dictionary<object, object>());
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Domain -> DTO
|
|
// ------------------------------------------------------------------
|
|
private static ClientDto MapClient(Client client, Dictionary<object, object> 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<object, object> 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<object, object> 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<object, object> 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)
|
|
// ------------------------------------------------------------------
|
|
|
|
/// <summary>Map a <see cref="Client"/> to its persistence record <see cref="ClientData"/>.</summary>
|
|
public static ClientData ToClientData(Client client)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(client);
|
|
return new ClientData { Id = client.Id, Name = client.Name };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Map an <see cref="Order"/> to its persistence record <see cref="OrderData"/>:
|
|
/// the object graph is flattened — <see cref="Order.Client"/> becomes the
|
|
/// <see cref="OrderData.ClientId"/> foreign key (empty when the order has
|
|
/// no client, or when that client has not been saved yet).
|
|
/// </summary>
|
|
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,
|
|
};
|
|
}
|
|
} |