Files
explicit-mapping/src/Before/DbBase.cs
T

59 lines
2.4 KiB
C#

namespace Before;
/// <summary>
/// Base class for every DB-backed domain object. Fakes the active-record part
/// of EF: each entity carries its own <see cref="Id"/> and, once it has been
/// created/saved through a context, a back-reference to that context.
///
/// <see cref="DbContext"/> is the dependency this base class leaks. The
/// "after" situation (Yak 02) removes this inheritance entirely — domain
/// objects become plain POCOs that know nothing about a DbContext.
/// </summary>
public class DbBase
{
/// <summary>
/// Primary key. Fresh (unsaved) entities have <see cref="Guid.Empty"/>;
/// <see cref="DbContext.Save"/> assigns a real id, faking EF's identity
/// generation.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Active-record back-reference: "I know which context created me". This
/// is the leak the exercise exposes — from a plain domain object you can
/// reach straight into the persistence layer. <c>internal set</c> because
/// only the owning <see cref="DbContext"/> may (re)assign it; consumers
/// (including the WebApp, and the tests) can only read it.
/// </summary>
public DbContext? DbContext { get; internal set; }
/// <summary>
/// Generic ActiveRecord-style finder: walks the <paramref name="db"/
/// />'s tracked entities, looks for the first whose runtime type matches
/// <typeparamref name="T"/> and satisfies <paramref name="predicate"/>
///.
///
/// This static generic rides the <c>DbContext</c> leak just like
/// <see cref="Client.FindByName"/>, but works for any <see cref="DbBase"
/// /> subtype without each entity needing its own hand-written finder.
/// </summary>
/// <typeparam name="T">Entity type to find (must derive from <see cref="DbBase"/>).
/// </typeparam>
/// <param name="predicate">Filter applied to candidates of type <typeparamref name="T"/>.
/// </param>
/// <param name="db">The context whose tracked entities to search.
/// </param>
/// <returns>The first matching entity, or <c>null</c> when no match.
/// </returns>
public static T? Find<T>(Predicate<T> predicate, DbContext? db) where T : DbBase
{
if (db is null)
return default;
foreach (var e in db.Tracked)
if (e is T candidate && predicate(candidate))
return candidate;
return default;
}
}