/// <summary> /// Register a projection to the Marten configuration /// </summary> /// <param name="projection">Value values are Inline/Async, The default is Inline</param> /// <param name="lifecycle"></param> /// <param name="projectionName">Overwrite the named identity of this projection. This is valuable if using the projection asynchonously</param> /// <param name="asyncConfiguration">Optional configuration including teardown instructions for the usage of this projection within the async projection daempon</param> public void Add(IProjection projection, ProjectionLifecycle lifecycle = ProjectionLifecycle.Inline, string projectionName = null, Action <AsyncOptions> asyncConfiguration = null) { if (lifecycle == ProjectionLifecycle.Live) { throw new ArgumentOutOfRangeException(nameof(lifecycle), $"{nameof(ProjectionLifecycle.Live)} cannot be used for IProjection"); } if (projection is ProjectionBase p) { p.AssembleAndAssertValidity(); p.Lifecycle = lifecycle; } if (projection is IProjectionSource source) { asyncConfiguration?.Invoke(source.Options); All.Add(source); } else { var wrapper = new ProjectionWrapper(projection, lifecycle); if (projectionName.IsNotEmpty()) { wrapper.ProjectionName = projectionName; } asyncConfiguration?.Invoke(wrapper.Options); All.Add(wrapper); } }
public async ValueTask ApplyChangesAsync(DocumentSessionBase session, EventSlice <TDoc, TId> slice, CancellationToken cancellation, ProjectionLifecycle lifecycle = ProjectionLifecycle.Inline) { if (Projection.MatchesAnyDeleteType(slice)) { var operation = Storage.DeleteForId(slice.Id, slice.Tenant.TenantId); session.QueueOperation(operation); return; } var aggregate = slice.Aggregate; if (slice.Aggregate == null && lifecycle == ProjectionLifecycle.Inline) { aggregate = await Storage.LoadAsync(slice.Id, session, cancellation).ConfigureAwait(false); } // Does the aggregate already exist before the events are applied? var exists = aggregate != null; foreach (var @event in slice.Events()) { try { aggregate = await ApplyEvent(session, slice, @event, aggregate, cancellation).ConfigureAwait(false); } catch (MartenCommandException) { throw; } catch (NpgsqlException) { throw; } catch (Exception e) { throw new ApplyEventException(@event, e); } } if (aggregate != null) { Storage.SetIdentity(aggregate, slice.Id); } // Delete the aggregate *if* it existed prior to these events if (aggregate == null) { if (exists) { var operation = Storage.DeleteForId(slice.Id, slice.Tenant.TenantId); session.QueueOperation(operation); } return; } session.QueueOperation(Storage.Upsert(aggregate, session, slice.Tenant.TenantId)); }
public override ValueTask ApplyChangesAsync(DocumentSessionBase session, EventSlice <CustomAggregate, int> slice, CancellationToken cancellation, ProjectionLifecycle lifecycle = ProjectionLifecycle.Inline) { var aggregate = slice.Aggregate ?? new CustomAggregate { Id = slice.Id }; foreach (var @event in slice.Events()) { if (@event.Data is CustomEvent e) { switch (e.Letter) { case 'a': aggregate.ACount++; break; case 'b': aggregate.BCount++; break; case 'c': aggregate.CCount++; break; case 'd': aggregate.DCount++; break; } } } session.Store(aggregate); return(new ValueTask()); }
/// <summary> /// Register a projection to the Marten configuration /// </summary> /// <param name="projection">Value values are Inline/Async, The default is Inline</param> /// <param name="lifecycle"></param> /// <param name="projectionName">Overwrite the named identity of this projection. This is valuable if using the projection asynchonously</param> /// <param name="asyncConfiguration">Optional configuration including teardown instructions for the usage of this projection within the async projection daempon</param> public void Add(IProjection projection, ProjectionLifecycle lifecycle = ProjectionLifecycle.Inline, string projectionName = null, Action <AsyncOptions> asyncConfiguration = null) { if (lifecycle == ProjectionLifecycle.Live) { throw new ArgumentOutOfRangeException(nameof(lifecycle), $"{nameof(ProjectionLifecycle.Live)} cannot be used for IProjection"); } var wrapper = new ProjectionWrapper(projection, lifecycle); if (projectionName.IsNotEmpty()) { wrapper.ProjectionName = projectionName; } asyncConfiguration?.Invoke(wrapper.Options); All.Add(wrapper); }
public override ValueTask ApplyChangesAsync(DocumentSessionBase session, EventSlice <StartAndStopAggregate, Guid> slice, CancellationToken cancellation, ProjectionLifecycle lifecycle = ProjectionLifecycle.Inline) { var aggregate = slice.Aggregate; foreach (var data in slice.AllData()) { switch (data) { case Start: aggregate = new StartAndStopAggregate { // Have to assign the identity ourselves Id = slice.Id }; break; case Increment when aggregate is { Deleted: false } : // Use explicit code to only apply this event // if the aggregate already exists aggregate.Increment(); break;
public override ValueTask ApplyChangesAsync(DocumentSessionBase session, EventSlice <CustomAggregate, int> slice, CancellationToken cancellation, ProjectionLifecycle lifecycle = ProjectionLifecycle.Inline) { throw new NotImplementedException(); }
public async Task <IStorageOperation?> DetermineOperation(DocumentSessionBase session, EventSlice <TDoc, TId> slice, CancellationToken cancellation, ProjectionLifecycle lifecycle = ProjectionLifecycle.Inline) { var aggregate = slice.Aggregate; if (slice.Aggregate == null && lifecycle == ProjectionLifecycle.Inline) { aggregate = await Storage.LoadAsync(slice.Id, session, cancellation).ConfigureAwait(false); } var exists = aggregate != null; foreach (var @event in slice.Events()) { try { aggregate = await ApplyEvent(session, slice, @event, aggregate, cancellation).ConfigureAwait(false); } catch (MartenCommandException) { throw; } catch (NpgsqlException) { throw; } catch (Exception e) { throw new ApplyEventException(@event, e); } } if (aggregate != null) { Storage.SetIdentity(aggregate, slice.Id); } if (aggregate == null) { return(exists ? Storage.DeleteForId(slice.Id, slice.Tenant) : null); } return(Storage.Upsert(aggregate, session, slice.Tenant)); }
public ProjectionWrapper(IProjection projection, ProjectionLifecycle lifecycle) : base(projection.GetType().FullNameInCode()) { _projection = projection; Lifecycle = lifecycle; }
/// <summary> /// Apply any document changes based on the incoming slice of events to the underlying aggregate document /// </summary> /// <param name="session"></param> /// <param name="slice"></param> /// <param name="cancellation"></param> /// <param name="lifecycle"></param> /// <returns></returns> public abstract ValueTask ApplyChangesAsync(DocumentSessionBase session, EventSlice <TDoc, TId> slice, CancellationToken cancellation, ProjectionLifecycle lifecycle = ProjectionLifecycle.Inline);
public async Task <IStorageOperation> DetermineOperation(DocumentSessionBase session, EventSlice <TDoc, TId> slice, CancellationToken cancellation, ProjectionLifecycle lifecycle = ProjectionLifecycle.Inline) { var aggregate = slice.Aggregate; if (slice.Aggregate == null && lifecycle == ProjectionLifecycle.Inline) { aggregate = await Storage.LoadAsync(slice.Id, session, cancellation); } var exists = aggregate != null; foreach (var @event in slice.Events) { aggregate = await ApplyEvent(session, slice, @event, aggregate, cancellation); } if (aggregate != null) { Storage.SetIdentity(aggregate, slice.Id); } if (aggregate == null) { return(exists ? Storage.DeleteForId(slice.Id, slice.Tenant) : null); } return(Storage.Upsert(aggregate, session, slice.Tenant)); }