DbBase.Find<T> now uses db ?? Context, and Client.FindByName's context
parameter becomes optional — call sites read like Client.FindByName("Jane
Doe") without context threading. A null result now overwhelmingly means
"no match"; the residual "no context configured at all" ambiguity is
documented in remarks as part of the ActiveRecord-leak cost this exercise
illustrates.
68 lines
2.4 KiB
C#
68 lines
2.4 KiB
C#
using System.Collections.ObjectModel;
|
|
|
|
namespace Before;
|
|
|
|
/// <summary>
|
|
/// A client, backed by the database: it derives from <see cref="DbBase"/> and
|
|
/// holds its orders as a navigation collection. In the "after" situation this
|
|
/// becomes a plain POCO (no <see cref="DbBase"/>) with a <c>ClientDto</c>
|
|
/// carrying its data to the WebApp.
|
|
/// </summary>
|
|
public class Client : DbBase
|
|
{
|
|
/// <summary>Display name of the client.</summary>
|
|
public string Name { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Navigation collection of this client's orders. Adding to it performs
|
|
/// EF-style navigation fix-up (see <see cref="ClientOrders"/>).
|
|
/// </summary>
|
|
public ClientOrders Orders { get; }
|
|
|
|
public Client()
|
|
{
|
|
Orders = new ClientOrders(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convenience lookup: finds the first <see cref="Client"/> whose
|
|
/// <see cref="Name"/> equals <paramref name="name"/>. Returns
|
|
/// <c>null</c> when no match.
|
|
///
|
|
/// This mirrors how an ActiveRecord-style ORM might surface a static
|
|
/// finder on the domain class itself — it rides the <c>DbContext</c>
|
|
/// leak from the entity's back-reference. Delegates to
|
|
/// <see cref="DbBase.Find{T}"/> for the common traversal logic.
|
|
///
|
|
/// The context is optional: when omitted, the <see cref="DbBase.Context"/>
|
|
/// singleton is used, so call sites read like
|
|
/// <c>Client.FindByName("Jane Doe")</c> — no context threading required.
|
|
/// </summary>
|
|
public static Client? FindByName(string name, DbContext? db = null)
|
|
=> DbBase.Find<Client>(c => c.Name == name, db);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An <see cref="Order"/> collection that fakes EF's navigation fix-up: when an
|
|
/// order is added, its <see cref="Order.Client"/> back-reference is set to the
|
|
/// owning client, exactly as EF would wire up the two ends of the relation.
|
|
/// </summary>
|
|
public sealed class ClientOrders : Collection<Order>
|
|
{
|
|
private readonly Client _owner;
|
|
|
|
public ClientOrders(Client owner)
|
|
{
|
|
_owner = owner;
|
|
}
|
|
|
|
// Both Add(...) and Insert(...) funnel through InsertItem, so overriding
|
|
// it covers every way an order can be added to the collection.
|
|
protected override void InsertItem(int index, Order item)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(item);
|
|
item.Client = _owner; // navigation fix-up: order now knows its client
|
|
base.InsertItem(index, item);
|
|
}
|
|
}
|