diff --git a/src/Before/DbContext.cs b/src/Before/DbContext.cs
index e11a3a3..225ad42 100644
--- a/src/Before/DbContext.cs
+++ b/src/Before/DbContext.cs
@@ -1,9 +1,11 @@
+using System.Collections.ObjectModel;
+
namespace Before;
///
-/// A tiny hand-rolled fake of EF's DbContext: it keeps a change-tracker
-/// (a registration collection of the entities it knows
-/// about) and a that fakes SaveChangesAsync.
+/// A tiny hand-rolled fake of EF's DbContext: it keeps per-type
+/// change-tracker tables (mirroring EF's pattern) and
+/// a that fakes SaveChangesAsync.
///
/// This is deliberately NOT real EF Core — no providers, no SQLite, no NuGet
/// beyond xUnit. The point of the exercise is the *shape of the dependencies*
@@ -11,25 +13,57 @@ namespace Before;
///
public class DbContext
{
- // The change tracker: the set of entities this context is responsible for.
- // (EF calls this its change tracker; a list stands in for the
- // id -> entity registration dictionary.)
- private readonly List _tracked = new();
+ // Per-type tables: each entity type has its own typed collection,
+ // mimicking EF Core's model where the DbContext
+ // maintains a separate set per entity type.
+ private readonly Dictionary> _tables = new();
+
+ ///
+ /// Get or create the per-type table for .
+ /// This mirrors EF's / Set<T> accessor.
+ ///
+ private Collection Table() where T : DbBase
+ {
+ var type = typeof(T);
+ if (!_tables.TryGetValue(type, out var table))
+ {
+ table = new Collection();
+ _tables[type] = table;
+ }
+ return table;
+ }
+
+ ///
+ /// Get or create the per-type table for the given runtime .
+ /// Called from where we only know the type at runtime.
+ ///
+ private Collection TableFor(Type type)
+ {
+ if (!_tables.TryGetValue(type, out var table))
+ {
+ table = new Collection();
+ _tables[type] = table;
+ }
+ return table;
+ }
///
/// Attach an entity to this context (EF's Add). Sets the
/// active-record back-reference so the entity knows its owner.
+ /// The entity is placed into the per-type table that matches its
+ /// runtime type — just as EF writes rows to the correct table.
///
public void Attach(DbBase entity)
{
ArgumentNullException.ThrowIfNull(entity);
entity.DbContext = this;
- if (!_tracked.Contains(entity))
- _tracked.Add(entity);
+ var table = TableFor(entity.GetType());
+ if (!table.Contains(entity))
+ table.Add(entity);
}
///
- /// Fake SaveChangesAsync: walk the tracked entities and assign a
+ /// Fake SaveChangesAsync: walk all per-type tables and assign a
/// fresh to any whose id is still .
/// Returns the number of entities that were (re)saved — i.e. newly
/// identified — mirroring EF's "rows written" return value.
@@ -37,20 +71,61 @@ public class DbContext
public int Save()
{
var saved = 0;
- foreach (var entity in _tracked)
- {
- if (entity.Id == Guid.Empty)
- {
- entity.Id = Guid.NewGuid();
- saved++;
- }
- }
+ foreach (var table in _tables.Values)
+ foreach (var entity in table)
+ if (entity.Id == Guid.Empty)
+ {
+ entity.Id = Guid.NewGuid();
+ saved++;
+ }
return saved;
}
- /// True if is in this context's tracker.
- public bool IsTracked(DbBase entity) => _tracked.Contains(entity);
+ ///
+ /// Find the first entity of type in this
+ /// context's per-type table that satisfies .
+ /// Only checks entities whose runtime type exactly matches
+ /// — just as EF's DbSet{T}.Find operates on a single table.
+ ///
+ /// This instance-level finder complements the static
+ /// .
+ ///
+ /// Entity type to find (must derive from ).
+ /// Filter applied to candidates of type .
+ /// The first matching entity, or null when no match.
+ public T? Find(Predicate predicate) where T : DbBase
+ {
+ var table = Table();
+ foreach (var e in table)
+ if (e is T candidate && predicate(candidate))
+ return candidate;
+ return default;
+ }
- /// Read-only view of the entities this context currently tracks.
- public IReadOnlyList Tracked => _tracked;
+ ///
+ /// True if is in this context's tracker
+ /// (walks all per-type tables).
+ ///
+ public bool IsTracked(DbBase entity)
+ {
+ foreach (var table in _tables.Values)
+ if (table.Contains(entity))
+ return true;
+ return false;
+ }
+
+ ///
+ /// Read-only view of all entities this context currently tracks
+ /// (flattened across all per-type tables).
+ ///
+ public IReadOnlyList Tracked
+ {
+ get
+ {
+ var all = new List();
+ foreach (var table in _tables.Values)
+ all.AddRange(table);
+ return all.AsReadOnly();
+ }
+ }
}