Yak: csharp after situation (Yak 02) — plain domain + DTOs + mappers
After/: plain Client/Order (no DbBase), ClientDto/OrderDto records carrying the relations both ways, hand-written Mappers with a reference-identity cache for the back-reference cycle, DbContext with explicit Save(domain) via shadow entities (flipped dependency), and a WebApp that consumes only DTOs. AfterTests cover the 7 required behaviours; full solution builds with 0 warnings, 26/26 tests pass.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
namespace After;
|
||||
|
||||
/// <summary>
|
||||
/// The fake <c>DbContext</c> of the after situation. The persistence layer
|
||||
/// still exists — but the direction of the dependency is flipped: domain
|
||||
/// objects are plain POCOs that know nothing about a context, and persisting
|
||||
/// one requires an *explicit* call into this context.
|
||||
///
|
||||
/// Saving a domain object registers it with a private shadow entity
|
||||
/// (a <see cref="DbBase"/> owned by this context only), fakes identity
|
||||
/// generation, and copies the new id back into the domain object. The domain
|
||||
/// object therefore ends up with a saved id, but with no back-reference to
|
||||
/// the context — the leak from the before situation is gone.
|
||||
/// </summary>
|
||||
public class DbContext
|
||||
{
|
||||
// Change tracker, keyed by the domain object's reference identity.
|
||||
private readonly Dictionary<object, DbBase> _tracked = new();
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly persist a <see cref="Client"/>: register it with a shadow
|
||||
/// entity, assign it a fresh <see cref="Guid"/> id, and copy that id into
|
||||
/// the client. Saving an already-saved client is a no-op for its id.
|
||||
/// </summary>
|
||||
public void Save(Client client)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(client);
|
||||
if (!_tracked.TryAdd(client, new DbBase { DbContext = this }))
|
||||
return; // already saved
|
||||
client.Id = _tracked[client].Id = Guid.NewGuid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly persist an <see cref="Order"/>: register it with a shadow
|
||||
/// entity, assign it a fresh <see cref="Guid"/> id, and copy that id into
|
||||
/// the order. Saving an already-saved order is a no-op for its id.
|
||||
/// </summary>
|
||||
public void Save(Order order)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(order);
|
||||
if (!_tracked.TryAdd(order, new DbBase { DbContext = this }))
|
||||
return; // already saved
|
||||
order.Id = _tracked[order].Id = Guid.NewGuid();
|
||||
}
|
||||
|
||||
/// <summary>True if <paramref name="entity"/> has been saved through this context.</summary>
|
||||
public bool IsTracked(object entity) => _tracked.ContainsKey(entity);
|
||||
|
||||
/// <summary>Read-only view of the shadow entities this context currently tracks.</summary>
|
||||
public IReadOnlyList<DbBase> Tracked => _tracked.Values.ToList();
|
||||
}
|
||||
Reference in New Issue
Block a user