identity read-back: FromOrderData/FromClientData + DbContext.LoadOrder/LoadClient
Plain field-copy materialization so the persistence round-trip closes. When OrderLines arrive, FromOrderData becomes the one-line reconstruction (row description + total become a single line) — the hinge Exercise 2 pivots on. yak: orderdata-read-back-identity-version-j9ew [order-data-mapping]
This commit is contained in:
@@ -115,6 +115,46 @@ public class DbContext
|
|||||||
_byRef[order] = fresh;
|
_byRef[order] = fresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── loading: reassemble domain objects from the stored rows ──────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Materialize the <see cref="Client"/> stored under <paramref name="id"/>,
|
||||||
|
/// including all of its orders (navigation fix-up wires
|
||||||
|
/// <see cref="Order.Client"/> back). Null when nothing is stored under it.
|
||||||
|
/// </summary>
|
||||||
|
public Client? LoadClient(Guid id)
|
||||||
|
{
|
||||||
|
var row = Find<ClientData>(r => r.Id == id);
|
||||||
|
if (row is null) return null;
|
||||||
|
var client = Mappers.FromClientData(row);
|
||||||
|
foreach (var orderRow in _orders.Where(o => o.ClientId == id))
|
||||||
|
client.Orders.Add(Mappers.FromOrderData(orderRow)); // fix-up wires Order.Client
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Materialize the <see cref="Order"/> stored under <paramref name="id"/>,
|
||||||
|
/// including its client (holding just this one order), or null when
|
||||||
|
/// nothing is stored under it. Use <see cref="LoadClient"/> to load a
|
||||||
|
/// client with all of its orders.
|
||||||
|
/// </summary>
|
||||||
|
public Order? LoadOrder(Guid id)
|
||||||
|
{
|
||||||
|
var row = Find<OrderData>(r => r.Id == id);
|
||||||
|
if (row is null) return null;
|
||||||
|
var order = Mappers.FromOrderData(row);
|
||||||
|
if (row.ClientId != Guid.Empty)
|
||||||
|
{
|
||||||
|
var clientRow = Find<ClientData>(c => c.Id == row.ClientId);
|
||||||
|
if (clientRow is not null)
|
||||||
|
{
|
||||||
|
var client = Mappers.FromClientData(clientRow);
|
||||||
|
client.Orders.Add(order); // fix-up wires Order.Client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
// ── query helpers ───────────────────────────────────────────────
|
// ── query helpers ───────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>Search the per-type table for a record whose predicate matches.</summary>
|
/// <summary>Search the per-type table for a record whose predicate matches.</summary>
|
||||||
|
|||||||
@@ -138,4 +138,29 @@ public static class Mappers
|
|||||||
ClientId = order.Client?.Id ?? Guid.Empty,
|
ClientId = order.Client?.Id ?? Guid.Empty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Persistence -> domain (read-back). A plain field copy for now; when
|
||||||
|
// the domain grows OrderLines, FromOrderData becomes the one-line
|
||||||
|
// reconstruction (row description + total become a single line) —
|
||||||
|
// see EXERCISE2.md.
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>Map a <see cref="ClientData"/> record back to a <see cref="Client"/>.</summary>
|
||||||
|
public static Client FromClientData(ClientData row)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(row);
|
||||||
|
return new Client { Id = row.Id, Name = row.Name };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Map an <see cref="OrderData"/> record back to an <see cref="Order"/>.
|
||||||
|
/// Reassembling the graph (wiring <see cref="Order.Client"/>) is the
|
||||||
|
/// context's job, not the mapper's.
|
||||||
|
/// </summary>
|
||||||
|
public static Order FromOrderData(OrderData row)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(row);
|
||||||
|
return new Order { Id = row.Id, Description = row.Description, TotalInCents = row.TotalInCents };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -88,4 +88,61 @@ public class AfterPersistenceTests
|
|||||||
Assert.Equal(order.Id, found!.Id);
|
Assert.Equal(order.Id, found!.Id);
|
||||||
Assert.Null(db.Find<OrderData>(row => row.Description == "no such order"));
|
Assert.Null(db.Find<OrderData>(row => row.Description == "no such order"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Read-back: LoadOrder / LoadClient reassemble domain objects from the
|
||||||
|
// stored rows (identity round-trip for now — OrderLines come later).
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loading_an_order_restores_its_state_and_its_client()
|
||||||
|
{
|
||||||
|
var db = new DbContext();
|
||||||
|
var client = new Client { Name = "Acme" };
|
||||||
|
var order = new Order { Description = "one", TotalInCents = 150_00 };
|
||||||
|
client.Orders.Add(order);
|
||||||
|
db.Save(client);
|
||||||
|
db.Save(order);
|
||||||
|
|
||||||
|
var loaded = db.LoadOrder(order.Id);
|
||||||
|
|
||||||
|
Assert.NotNull(loaded);
|
||||||
|
Assert.Equal(order.Id, loaded!.Id);
|
||||||
|
Assert.Equal("one", loaded.Description);
|
||||||
|
Assert.Equal(150_00, loaded.TotalInCents);
|
||||||
|
|
||||||
|
// The graph is reassembled: the loaded order knows its client again,
|
||||||
|
// and the client's collection holds the loaded order.
|
||||||
|
Assert.NotNull(loaded.Client);
|
||||||
|
Assert.Equal("Acme", loaded.Client!.Name);
|
||||||
|
Assert.Same(loaded, Assert.Single(loaded.Client.Orders));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loading_a_client_restores_it_with_all_of_its_orders()
|
||||||
|
{
|
||||||
|
var db = new DbContext();
|
||||||
|
var client = new Client { Name = "Acme" };
|
||||||
|
client.Orders.Add(new Order { Description = "one", TotalInCents = 150_00 });
|
||||||
|
client.Orders.Add(new Order { Description = "two", TotalInCents = 250_00 });
|
||||||
|
db.Save(client);
|
||||||
|
db.Save(client.Orders[0]);
|
||||||
|
db.Save(client.Orders[1]);
|
||||||
|
|
||||||
|
var loaded = db.LoadClient(client.Id);
|
||||||
|
|
||||||
|
Assert.NotNull(loaded);
|
||||||
|
Assert.Equal("Acme", loaded!.Name);
|
||||||
|
Assert.Equal(2, loaded.Orders.Count);
|
||||||
|
Assert.All(loaded.Orders, o => Assert.Same(loaded, o.Client));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Loading_an_unknown_id_returns_null()
|
||||||
|
{
|
||||||
|
var db = new DbContext();
|
||||||
|
|
||||||
|
Assert.Null(db.LoadOrder(Guid.NewGuid()));
|
||||||
|
Assert.Null(db.LoadClient(Guid.NewGuid()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user