Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 23 additions & 45 deletions src/Marten/Internal/ClosedShape/ClosedShapeDirtyTrackingSelector.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Threading;
Expand All @@ -12,29 +11,27 @@
namespace Marten.Internal.ClosedShape;

/// <summary>
/// W3 spike (M2): <see cref="ISelector{T}"/> for the
/// <see cref="DirtyCheckedSequentialGuidStorage{TDoc}"/> path.
/// Identity-map writes (like <see cref="ClosedShapeIdentityMapSelector{T, TId}"/>)
/// plus a <see cref="ChangeTracker{T}"/> registered on the session for
/// every loaded document — gives <c>SaveChangesAsync</c> a baseline to
/// compare against when dirty-checking which loaded docs were modified.
/// Abstract base for the per-<see cref="ConcurrencyMode"/> closed-shape
/// DirtyTracking <see cref="ISelector{T}"/>. Identity-map writes (like
/// <see cref="ClosedShapeIdentityMapSelector{T, TId}"/>) plus a
/// <see cref="ChangeTracker{T}"/> registered on the session per loaded
/// document. Sealed concurrency-specific subclasses override
/// <c>CaptureVersion</c> (#4659).
/// </summary>
internal sealed class ClosedShapeDirtyTrackingSelector<T, TId>: ISelector<T>, IDocumentSelector
internal abstract class ClosedShapeDirtyTrackingSelector<T, TId>: ISelector<T>, IDocumentSelector
where T : notnull
where TId : notnull
{
private const int IdColumn = 0;
private const int DataColumn = 1;
private const int FirstMetadataColumn = 2;
protected const int IdColumn = 0;
protected const int DataColumn = 1;
protected const int FirstMetadataColumn = 2;

private readonly IMartenSession _session;
private readonly ISerializer _serializer;
private readonly DocumentStorageDescriptor<T, TId> _descriptor;
private readonly Dictionary<TId, T> _identityMap;
private readonly Dictionary<TId, Guid>? _versions;
private readonly Dictionary<TId, long>? _revisions;
protected readonly IMartenSession _session;
protected readonly ISerializer _serializer;
protected readonly DocumentStorageDescriptor<T, TId> _descriptor;
protected readonly Dictionary<TId, T> _identityMap;

public ClosedShapeDirtyTrackingSelector(IMartenSession session, DocumentStorageDescriptor<T, TId> descriptor)
protected ClosedShapeDirtyTrackingSelector(IMartenSession session, DocumentStorageDescriptor<T, TId> descriptor)
{
_session = session;
_serializer = session.Serializer;
Expand All @@ -49,13 +46,6 @@ public ClosedShapeDirtyTrackingSelector(IMartenSession session, DocumentStorageD
_identityMap = new Dictionary<TId, T>();
session.ItemMap[typeof(T)] = _identityMap;
}

_versions = descriptor.ConcurrencyMode == ConcurrencyMode.Optimistic
? session.Versions.ForType<T, TId>()
: null;
_revisions = descriptor.ConcurrencyMode == ConcurrencyMode.Numeric
? session.Versions.RevisionsFor<T, TId>()
: null;
}

public T Resolve(DbDataReader reader)
Expand Down Expand Up @@ -96,7 +86,12 @@ public async Task<T> ResolveAsync(DbDataReader reader, CancellationToken token)
return doc;
}

private T ReadDocument(DbDataReader reader)
/// <summary>
/// Concurrency-specific per-row version capture.
/// </summary>
protected abstract void CaptureVersion(DbDataReader reader, TId id);

protected T ReadDocument(DbDataReader reader)
{
if (_descriptor.HierarchyMapping is { } hierarchy)
{
Expand All @@ -106,7 +101,7 @@ private T ReadDocument(DbDataReader reader)
return _serializer.FromJson<T>(reader, DataColumn);
}

private async System.Threading.Tasks.ValueTask<T> ReadDocumentAsync(DbDataReader reader, CancellationToken token)
protected async System.Threading.Tasks.ValueTask<T> ReadDocumentAsync(DbDataReader reader, CancellationToken token)
{
if (_descriptor.HierarchyMapping is { } hierarchy)
{
Expand All @@ -116,7 +111,7 @@ private async System.Threading.Tasks.ValueTask<T> ReadDocumentAsync(DbDataReader
return await _serializer.FromJsonAsync<T>(reader, DataColumn, token).ConfigureAwait(false);
}

private void ApplyMetadata(DbDataReader reader, T document)
protected void ApplyMetadata(DbDataReader reader, T document)
{
var ordinal = FirstMetadataColumn;
foreach (var binder in _descriptor.ReadBinders)
Expand All @@ -125,21 +120,4 @@ private void ApplyMetadata(DbDataReader reader, T document)
ordinal++;
}
}

private void CaptureVersion(DbDataReader reader, TId id)
{
var versionIndex = _descriptor.VersionReadIndex;
if (versionIndex < 0) return;
var versionOrdinal = FirstMetadataColumn + versionIndex;
if (reader.IsDBNull(versionOrdinal)) return;

if (_versions is not null)
{
_versions[id] = reader.GetFieldValue<Guid>(versionOrdinal);
}
else if (_revisions is not null)
{
_revisions[id] = reader.GetFieldValue<long>(versionOrdinal);
}
}
}
75 changes: 23 additions & 52 deletions src/Marten/Internal/ClosedShape/ClosedShapeIdentityMapSelector.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Threading;
Expand All @@ -11,37 +10,27 @@
namespace Marten.Internal.ClosedShape;

/// <summary>
/// W3 spike (M2): <see cref="ISelector{T}"/> for the
/// <see cref="IdentityMapSequentialGuidStorage{TDoc}"/> path. Reads
/// id+data+metadata and writes <c>(id, doc)</c> into the session's
/// identity-map dictionary so subsequent <c>LoadAsync</c> calls
/// short-circuit to the in-memory instance.
/// Abstract base for the per-<see cref="ConcurrencyMode"/> closed-shape
/// IdentityMap <see cref="ISelector{T}"/>. Owns the identity-map cache
/// init + lookup; sealed subclasses provide a monomorphic
/// <c>CaptureVersion</c> override so the per-row hot path doesn't read
/// <c>ConcurrencyMode</c> (#4659).
/// </summary>
/// <remarks>
/// The identity map lives on <see cref="IMartenSession.ItemMap"/> keyed by
/// document type. The selector acquires it (or creates a fresh one) at
/// construction so per-row writes don't re-walk the dictionary lookup.
/// Mirrors what the codegen-emitted <c>DocumentSelectorWithIdentityMap</c>
/// subclass does today.
/// </remarks>
internal sealed class ClosedShapeIdentityMapSelector<T, TId>: ISelector<T>, IDocumentSelector
internal abstract class ClosedShapeIdentityMapSelector<T, TId>: ISelector<T>, IDocumentSelector
where T : notnull
where TId : notnull
{
// Same column layout as Lightweight: id at 0, data at 1, metadata at 2+.
// IdColumn.ShouldSelect is true for all non-QueryOnly styles.
private const int IdColumn = 0;
private const int DataColumn = 1;
private const int FirstMetadataColumn = 2;
protected const int IdColumn = 0;
protected const int DataColumn = 1;
protected const int FirstMetadataColumn = 2;

private readonly IMartenSession _session;
private readonly ISerializer _serializer;
private readonly DocumentStorageDescriptor<T, TId> _descriptor;
private readonly Dictionary<TId, T> _identityMap;
private readonly Dictionary<TId, Guid>? _versions;
private readonly Dictionary<TId, long>? _revisions;
protected readonly IMartenSession _session;
protected readonly ISerializer _serializer;
protected readonly DocumentStorageDescriptor<T, TId> _descriptor;
protected readonly Dictionary<TId, T> _identityMap;

public ClosedShapeIdentityMapSelector(IMartenSession session, DocumentStorageDescriptor<T, TId> descriptor)
protected ClosedShapeIdentityMapSelector(IMartenSession session, DocumentStorageDescriptor<T, TId> descriptor)
{
_session = session;
_serializer = session.Serializer;
Expand All @@ -56,13 +45,6 @@ public ClosedShapeIdentityMapSelector(IMartenSession session, DocumentStorageDes
_identityMap = new Dictionary<TId, T>();
session.ItemMap[typeof(T)] = _identityMap;
}

_versions = descriptor.ConcurrencyMode == ConcurrencyMode.Optimistic
? session.Versions.ForType<T, TId>()
: null;
_revisions = descriptor.ConcurrencyMode == ConcurrencyMode.Numeric
? session.Versions.RevisionsFor<T, TId>()
: null;
}

public T Resolve(DbDataReader reader)
Expand Down Expand Up @@ -106,7 +88,13 @@ public async Task<T> ResolveAsync(DbDataReader reader, CancellationToken token)
return doc;
}

private T ReadDocument(DbDataReader reader)
/// <summary>
/// Concurrency-specific per-row version capture. Off-mode subclasses
/// no-op; Optimistic / Numeric capture into their typed tracker.
/// </summary>
protected abstract void CaptureVersion(DbDataReader reader, TId id);

protected T ReadDocument(DbDataReader reader)
{
if (_descriptor.HierarchyMapping is { } hierarchy)
{
Expand All @@ -116,7 +104,7 @@ private T ReadDocument(DbDataReader reader)
return _serializer.FromJson<T>(reader, DataColumn);
}

private async System.Threading.Tasks.ValueTask<T> ReadDocumentAsync(DbDataReader reader, CancellationToken token)
protected async System.Threading.Tasks.ValueTask<T> ReadDocumentAsync(DbDataReader reader, CancellationToken token)
{
if (_descriptor.HierarchyMapping is { } hierarchy)
{
Expand All @@ -126,7 +114,7 @@ private async System.Threading.Tasks.ValueTask<T> ReadDocumentAsync(DbDataReader
return await _serializer.FromJsonAsync<T>(reader, DataColumn, token).ConfigureAwait(false);
}

private void ApplyMetadata(DbDataReader reader, T document)
protected void ApplyMetadata(DbDataReader reader, T document)
{
var ordinal = FirstMetadataColumn;
foreach (var binder in _descriptor.ReadBinders)
Expand All @@ -135,21 +123,4 @@ private void ApplyMetadata(DbDataReader reader, T document)
ordinal++;
}
}

private void CaptureVersion(DbDataReader reader, TId id)
{
var versionIndex = _descriptor.VersionReadIndex;
if (versionIndex < 0) return;
var versionOrdinal = FirstMetadataColumn + versionIndex;
if (reader.IsDBNull(versionOrdinal)) return;

if (_versions is not null)
{
_versions[id] = reader.GetFieldValue<Guid>(versionOrdinal);
}
else if (_revisions is not null)
{
_revisions[id] = reader.GetFieldValue<long>(versionOrdinal);
}
}
}
Loading
Loading