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
8 changes: 8 additions & 0 deletions docs/events/dcb.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ opts.Events.RegisterTagType<TicketId>("custom_ticket")
opts.Projections.Add<TicketSummaryProjection>(ProjectionLifecycle.Inline);
```

Every discovered tag type adds a table, with its foreign key and indexes, and a parameter on `mt_quick_append_events`, whether or not you ever write a tag. If you use strong-typed identifiers but not DCB, turn auto-discovery off:

```csharp
opts.Events.AutoDiscoverTagTypes = false;
```

Explicit `RegisterTagType<T>()` calls keep working with auto-discovery off. Turning it off on an existing database leaves any tag tables already there in place, and Marten will not remove them for you.

### Registering Tag Types under Trimming or Native AOT

`RegisterTagType<TTag>()` discovers the primitive inside your tag type by reflecting over its
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;
using JasperFx.Events;
using JasperFx.Events.Projections;
using JasperFx.Events.Tags;
using Marten;
using Marten.Events;
using Marten.Events.Aggregation;
using Marten.Events.Dcb;
Expand Down Expand Up @@ -86,6 +88,51 @@ public void primitive_identity_types_are_not_auto_registered()
theStore.Events.FindTagType(typeof(Guid)).ShouldBeNull();
}

[Fact]
public void auto_discovery_is_on_by_default()
{
new StoreOptions().EventGraph.AutoDiscoverTagTypes.ShouldBeTrue();
}

[Fact]
public async Task nothing_is_registered_or_built_when_auto_discovery_is_turned_off()
{
StoreOptions(opts =>
{
opts.Events.AutoDiscoverTagTypes = false;
opts.Projections.Add<TicketSummaryProjection>(ProjectionLifecycle.Inline);
});

theStore.Events.FindTagType(typeof(TicketId)).ShouldBeNull();
theStore.Events.TagTypes.ShouldBeEmpty();

var streamId = Guid.NewGuid();
theSession.Events.StartStream(streamId, new TicketOpened("Fix bug"));
await theSession.SaveChangesAsync();

// The point of the option, so read it from the catalog rather than from the configuration
await using var query = theStore.QuerySession();
var tagTables = await query.AdvancedSql.QueryAsync<string>(
"select table_name from information_schema.tables where table_schema = ? and table_name like 'mt_event_tag%'",
CancellationToken.None, theStore.Options.DatabaseSchemaName);
tagTables.ShouldBeEmpty();
}

[Fact]
public void explicit_registration_still_works_when_auto_discovery_is_turned_off()
{
StoreOptions(opts =>
{
opts.Events.AutoDiscoverTagTypes = false;
opts.Events.RegisterTagType<TicketId>("custom_ticket").ForAggregate<TicketSummary>();
opts.Projections.Add<TicketSummaryProjection>(ProjectionLifecycle.Inline);
});

var registration = theStore.Events.FindTagType(typeof(TicketId));
registration.ShouldNotBeNull();
registration.TableSuffix.ShouldBe("custom_ticket");
}

[Fact]
public async Task auto_discovered_tag_type_works_for_querying()
{
Expand Down
8 changes: 7 additions & 1 deletion src/Marten/Events/EventGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,12 @@ internal static string TagTypeRegistrationLine(Type tagType, string? tableSuffix
/// </summary>
public IReadOnlyList<ITagTypeRegistration> TagTypes => _tagTypes;

/// <summary>
/// Whether the strong-typed identifier of an aggregate projection is automatically registered as a
/// tag type. Defaults to true. Explicit registrations work either way.
/// </summary>
public bool AutoDiscoverTagTypes { get; set; } = true;

/// <summary>
/// Derive a DCB tag from an event's own data, for every event appended through this store.
/// <para>
Expand Down Expand Up @@ -1297,7 +1303,7 @@ internal void Initialize(DocumentStore store)

private void autoDiscoverTagTypesFromProjections(ILogger logger)
{
if (CreateTagTypeMethod == null) return;
if (!AutoDiscoverTagTypes || CreateTagTypeMethod == null) return;

foreach (var projection in Options.Projections.All.OfType<IAggregateProjection>())
{
Expand Down
9 changes: 9 additions & 0 deletions src/Marten/Events/IEventStoreOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,15 @@ public IEventStoreOptions Upcast<TOldEvent, TEvent>(
/// <typeparam name="T"></typeparam>
void AddMaskingRuleForProtectedInformation<T>(Func<T, T> func);

/// <summary>
/// Whether the strong-typed identifier of an aggregate projection is automatically registered as a
/// tag type for Dynamic Consistency Boundary (DCB) support. Each one gets a tag table, with its
/// foreign key and indexes, and a parameter on <c>mt_quick_append_events</c>. Turn this off if you
/// use strong-typed identifiers but not DCB. Explicit <see cref="RegisterTagType{TTag}()"/> calls
/// work either way. Defaults to true.
/// </summary>
bool AutoDiscoverTagTypes { get; set; }

/// <summary>
/// Register a strong-typed identifier as a tag type for Dynamic Consistency Boundary (DCB) support.
/// This creates a dedicated tag table for efficient cross-stream querying and consistency checks.
Expand Down
Loading