JasperFx / marten

.NET Transactional Document DB and Event Store on PostgreSQL
https://martendb.io
MIT License
2.79k stars 441 forks source link

Having a projection configuration in a class named Projection leads to compilation exception #3184

Closed mrnustik closed 4 months ago

mrnustik commented 4 months ago

Hi, I have put my projection configuration classes inside the projected class and named them Projection. Marten finds the projection well, but unfortunately, I have run into a compilation exception from the projection code generation. I have managed to track down the code, that generates it but unfortunately, I was not able to create the fix myself.

Projection registration

storeOptions.Projections.Add<AccountListInformation.Projection>(ProjectionLifecycle.Inline)

Projection code

public class AccountListInformation
{
    public Guid Id { get; set; }
    public AccountId AccountId { get; private set; }
    public string Name { get; private set; }
    public decimal Balance { get; private set; }

    public void Apply(AccountCreated accountCreated)
    {
        Id = accountCreated.Id.Value;
        AccountId = accountCreated.Id;
        Name = accountCreated.Name;
        Balance = 0;
    }

    public class Projection : SingleStreamProjection<AccountListInformation>
    {
        public Projection()
        {
            ProjectEvent<AccountCreated>((i, e) => i.Apply(e));
        }
    }
}

Exception output along with the code generation error

System.InvalidOperationException: Compilation failures!

CS0103: The name 'projection' does not exist in the current context

Code:

// <auto-generated/>
#pragma warning disable
using Expensifier.API.Accounts.GetAccountsById;
using Marten;
using Marten.Events.Aggregation;
using Marten.Internal.Storage;
using System;
using System.Linq;

namespace Marten.Generated.EventStore
{
    // START: ProjectionLiveAggregation260415182
    public class ProjectionLiveAggregation260415182 : Marten.Events.Aggregation.SyncLiveAggregatorBase<Expensifier.API.Accounts.GetAccountsById.AccountListInformation>
    {
        private readonly Expensifier.API.Accounts.GetAccountsById.AccountListInformation.Projection _projection;

        public ProjectionLiveAggregation260415182(Expensifier.API.Accounts.GetAccountsById.AccountListInformation.Projection projection)
        {
            _projection = projection;
        }

        public System.Action<Expensifier.API.Accounts.GetAccountsById.AccountListInformation, Expensifier.API.Accounts.Domain.AccountCreated> ProjectEvent1 {get; set;}

        public override Expensifier.API.Accounts.GetAccountsById.AccountListInformation Build(System.Collections.Generic.IReadOnlyList<Marten.Events.IEvent> events, Marten.IQuerySession session, Expensifier.API.Accounts.GetAccountsById.AccountListInformation snapshot)
        {
            if (!events.Any()) return snapshot;
            var usedEventOnCreate = snapshot is null;
            snapshot ??= Create(events[0], session);;
            if (snapshot is null)
            {
                usedEventOnCreate = false;
                snapshot = CreateDefault(events[0]);
            }

            foreach (var @event in events.Skip(usedEventOnCreate ? 1 : 0))
            {
                snapshot = Apply(@event, snapshot, session);
            }

            return snapshot;
        }

        public Expensifier.API.Accounts.GetAccountsById.AccountListInformation Create(Marten.Events.IEvent @event, Marten.IQuerySession session)
        {
            return null;
        }

        public Expensifier.API.Accounts.GetAccountsById.AccountListInformation Apply(Marten.Events.IEvent @event, Expensifier.API.Accounts.GetAccountsById.AccountListInformation aggregate, Marten.IQuerySession session)
        {
            switch (@event)
            {
                case Marten.Events.IEvent<Expensifier.API.Accounts.Domain.AccountCreated> event_AccountCreated1:
                    aggregate.Apply(event_AccountCreated1.Data);
                    ProjectEvent1.Invoke(aggregate, event_AccountCreated1.Data);
                    break;
            }

            return aggregate;
        }

    }

    // END: ProjectionLiveAggregation260415182

    // START: ProjectionInlineHandler260415182
    public class ProjectionInlineHandler260415182 : Marten.Events.Aggregation.AggregationRuntime<Expensifier.API.Accounts.GetAccountsById.AccountListInformation, System.Guid>
    {
        private readonly Marten.IDocumentStore _store;
        private readonly Marten.Events.Aggregation.IAggregateProjection _projection1;
        private readonly Marten.Events.Aggregation.IEventSlicer<Expensifier.API.Accounts.GetAccountsById.AccountListInformation, System.Guid> _slicer;
        private readonly Marten.Internal.Storage.IDocumentStorage<Expensifier.API.Accounts.GetAccountsById.AccountListInformation, System.Guid> _storage;
        private readonly Expensifier.API.Accounts.GetAccountsById.AccountListInformation.Projection _projection2;

        public ProjectionInlineHandler260415182(Marten.IDocumentStore store, Marten.Events.Aggregation.IAggregateProjection __projection1, Marten.Events.Aggregation.IEventSlicer<Expensifier.API.Accounts.GetAccountsById.AccountListInformation, System.Guid> slicer, Marten.Internal.Storage.IDocumentStorage<Expensifier.API.Accounts.GetAccountsById.AccountListInformation, System.Guid> storage, Expensifier.API.Accounts.GetAccountsById.AccountListInformation.Projection __projection2) : base(store, projection, slicer, storage)
        {
            _store = store;
            _projection1 = __projection1;
            _slicer = slicer;
            _storage = storage;
            _projection2 = __projection2;
        }

        public System.Action<Expensifier.API.Accounts.GetAccountsById.AccountListInformation, Expensifier.API.Accounts.Domain.AccountCreated> ProjectEvent1 {get; set;}

        public override async System.Threading.Tasks.ValueTask<Expensifier.API.Accounts.GetAccountsById.AccountListInformation> ApplyEvent(Marten.IQuerySession session, Marten.Events.Projections.EventSlice<Expensifier.API.Accounts.GetAccountsById.AccountListInformation, System.Guid> slice, Marten.Events.IEvent evt, Expensifier.API.Accounts.GetAccountsById.AccountListInformation aggregate, System.Threading.CancellationToken cancellationToken)
        {
            switch (evt)
            {
                case Marten.Events.IEvent<Expensifier.API.Accounts.Domain.AccountCreated> event_AccountCreated2:
                    aggregate ??= CreateDefault(evt);
                    ProjectEvent1.Invoke(aggregate, event_AccountCreated2.Data);
                    return aggregate;
            }

            return aggregate;
        }

        public Expensifier.API.Accounts.GetAccountsById.AccountListInformation Create(Marten.Events.IEvent @event, Marten.IQuerySession session)
        {
            return null;
        }

    }

    // END: ProjectionInlineHandler260415182

}

   at JasperFx.RuntimeCompiler.AssemblyGenerator.Generate(String code)
   at JasperFx.RuntimeCompiler.AssemblyGenerator.Compile(GeneratedAssembly generatedAssembly, IServiceVariableSource services)
   at JasperFx.RuntimeCompiler.CodeFileExtensions.InitializeSynchronously(ICodeFile file, GenerationRules rules, ICodeFileCollection parent, IServiceProvider services)
   at Marten.Events.Projections.GeneratedProjection.<generateIfNecessary>g__generateIfNecessaryLocked|21_0(<>c__DisplayClass21_0&)
   at Marten.Events.Projections.GeneratedProjection.generateIfNecessary(DocumentStore store)
   at Marten.Events.Projections.GeneratedProjection.Marten.Events.Projections.IProjectionSource.Build(DocumentStore store)
   at Marten.Events.Projections.ProjectionOptions.<>c__DisplayClass23_0.<BuildInlineProjections>b__1(IProjectionSource x)
   at System.Linq.Enumerable.SelectArrayIterator`2.Fill(ReadOnlySpan`1 source, Span`1 destination, Func`2 func)
   at System.Linq.Enumerable.SelectArrayIterator`2.ToArray()
   at Marten.Events.Projections.ProjectionOptions.BuildInlineProjections(DocumentStore store)
   at Marten.Events.EventGraph.<>c__DisplayClass16_0.<.ctor>b__2()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at Marten.Events.EventGraph.ProcessEventsAsync(DocumentSessionBase session, CancellationToken token)
   at Marten.Internal.Sessions.DocumentSessionBase.SaveChangesAsync(CancellationToken token)
   at Marten.Internal.Sessions.DocumentSessionBase.SaveChangesAsync(CancellationToken token)
   at Expensifier.API.Accounts.CreateAccount.CreateAccountCommand.Handler.Execute(CreateAccountCommand command, CancellationToken cancellationToken) in F:\Development\Expensifier\api\Expensifier.API\Accounts\CreateAccount\CreateAccountCommand.cs:line 29
   at Expensifier.API.Accounts.Endpoints.<>c.<<AddAccountEndpoints>b__0_0>d.MoveNext() in F:\Development\Expensifier\api\Expensifier.API\Accounts\Endpoints.cs:line 18
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.ExecuteTaskResult[T](Task`1 task, HttpContext httpContext)
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass102_2.<<HandleRequestBodyAndCompileRequestDelegateForJson>b__2>d.MoveNext()
--- End of stack trace from previous location ---
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
mrnustik commented 4 months ago

The issue seems to be in the underlying code generation code from the JasperFx.CodeGeneration repository. I have created an issue there as well.

jeremydmiller commented 4 months ago

@mrnustik So for a quick work around, literally call your Projection class any other valid C# name other than Projection

mrnustik commented 4 months ago

@jeremydmiller thanks for such a quick response and fix. I have already changed my projection name, but will test it once it gets out. 🙂