JasperFx / marten

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

LINQ Serialization fails to use json arrows for data field #3304

Closed SilvieM closed 1 month ago

SilvieM commented 1 month ago

Hello, I came across an issue with LINQ serialization. I'm trying to strip it down to only the involved parts. My classes are:

public class ThingProjection : SingleStreamProjection<Thing>
{
 public ThingProjection()
    {
        IncludeType<ThingInitialized>();
        IncludeType<ThingFailed>();
        IncludeType<Deleted>();
    }
   //create methods and apply methods here
}

public class Thing: Aggregate
{
    public ThingState State { get; private set; }
    //some other data fields
    //many methods here that are called from the projection to alter fields
}

public enum ThingState
{
    Draft,
    Initialized,
    Failed,
    Completed,
    Revoked
}

Now when I request entries of Thing that have a certain state, I should be able to do: var revokedThings = querySession.Query<Thing>().Where(it => it.State == ThingState.Revoked).ToList();

But it serializes this to

Marten executed in 0 ms, SQL: select d.data from public.mt_doc_thing as d where d.state = :p0
  p0: Revoked

And that returns empty, because the data column contains json. It doesnt even give me an error, just an empty list. A working query would go: select d.data from public.mt_doc_thing as d where data ->> 'State' = 'Revoked';

Requesting all with .toList() and then adding the Where afterwards so it is evaluated locally works fine and gives me results.

We have some custom serializer settings

           var serializer = new JsonNetSerializer { EnumStorage = EnumStorage.AsString };
            serializer.Customize(s =>
            {
                s.ContractResolver = new NonDefaultConstructorMartenJsonNetContractResolver(
                    Casing.Default,
                    CollectionStorage.Default,
                    NonPublicMembersStorage.NonPublicSetters
                );
            });
            options.Serializer(serializer);
public class NonDefaultConstructorMartenJsonNetContractResolver : JsonNetContractResolver
{
    public NonDefaultConstructorMartenJsonNetContractResolver(
        Casing casing,
        CollectionStorage collectionStorage,
        NonPublicMembersStorage nonPublicMembersStorage = NonPublicMembersStorage.Default) :
        base(casing, collectionStorage, nonPublicMembersStorage)
    {
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType) =>
        JsonObjectContractProvider.UsingNonDefaultConstructor(
            base.CreateObjectContract(objectType),
            objectType,
            base.CreateConstructorParameters
        );
}

Relevant Versions: .NET 8.0 Marten 6.4.1 Newtonsoft.Json 13.0.3 NPGSQL.Json.NET 7.0.6

jeremydmiller commented 1 month ago

@SilvieM Are you maybe accidentally using a duplicated field for the State property up above? That's the most likely explanation. That exact use case is pretty well covered today in the Marten LINQ tests

SilvieM commented 1 month ago

Ooof. How embarassing. Yes I am. I wasnt aware that fields can be duplicated that way and never scrolled all the way to the right in the db. Thanks for the help.