ravendb / ravendb

ACID Document Database
https://ravendb.net
Other
3.55k stars 820 forks source link

Cannot perform ReadEndArray when encountered the ReadObjectDocument state #11841

Closed NotoriousPyro closed 3 years ago

NotoriousPyro commented 3 years ago

I have the following exception being thrown am I'm not 100% sure on the cause of it...

The code works when I implement my items as a List passed through the program but when I try to make a custom class so I can remove the List from everywhere and replace it with my class that implements IEnumerable or IList, it throws this exception.

When the object is constructed elsewhere, it looks fine but as soon as raven client tries to serialize it this exception happens...

I thought this would work and would be transformed into a list of objects but am I missing something?

This exception was originally thrown at this call stack:
    Sparrow.Json.ManualBlittableJsonDocumentBuilder<TWriter>.ThrowIllegalStateException(Sparrow.Json.AbstractBlittableJsonDocumentBuilder.ContinuationState, string) in ManualBlittableJsonDocumentBuilder.cs
    Sparrow.Json.ManualBlittableJsonDocumentBuilder<TWriter>.WriteArrayEnd() in ManualBlittableJsonDocumentBuilder.cs
    Raven.Client.Json.Serialization.NewtonsoftJson.Internal.BlittableJsonWriter.WriteEndArray() in BlittableJsonWriter.cs
    Raven.Client.Json.Serialization.NewtonsoftJson.Internal.Converters.CachingJsonConverter.WriteJson(Newtonsoft.Json.JsonWriter, object, Newtonsoft.Json.JsonSerializer) in CachingJsonConverter.cs
    Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeConvertable(Newtonsoft.Json.JsonWriter, Newtonsoft.Json.JsonConverter, object, Newtonsoft.Json.Serialization.JsonContract, Newtonsoft.Json.Serialization.JsonContainerContract, Newtonsoft.Json.Serialization.JsonProperty)
    Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue(Newtonsoft.Json.JsonWriter, object, Newtonsoft.Json.Serialization.JsonContract, Newtonsoft.Json.Serialization.JsonProperty, Newtonsoft.Json.Serialization.JsonContainerContract, Newtonsoft.Json.Serialization.JsonProperty)
    Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(Newtonsoft.Json.JsonWriter, object, System.Type)
    Newtonsoft.Json.JsonSerializer.SerializeInternal(Newtonsoft.Json.JsonWriter, object, System.Type)
    Newtonsoft.Json.JsonSerializer.Serialize(Newtonsoft.Json.JsonWriter, object)
    Raven.Client.Json.Serialization.NewtonsoftJson.Internal.NewtonsoftJsonJsonSerializer.Raven.Client.Json.Serialization.IJsonSerializer.Serialize(Raven.Client.Json.Serialization.IJsonWriter, object) in NewtonsoftJsonJsonSerializer.cs
    ...
    [Call Stack Truncated]

The code:

    internal class Collection<T> : IEnumerable<T>, IEnumerable where T : class
    {
        internal List<T> Items { get; set; }

        internal Collection()
        {
            Items = new List<T>();
        }

        internal Collection(List<T> items)
        {
            Items = items;
        }

        public void Add(T item)
        {
            Items.Add(item);
        }

        public IEnumerator<T> GetEnumerator()
        {
            return Items.GetEnumerator();
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    internal class Symbols : Collection<Symbol>
    {
        internal Symbols()
        {
            Items = new List<Symbol>();
        }

        internal Symbols(List<Symbol> allSymbols)
        {
            Items = allSymbols;
        }
    }

    private void InsertSymbols(Symbols symbols, string databaseName)
    {
        using var session = Store.OpenSession(databaseName);

        var existing = session.Load<Symbols>("Symbols");
        if (existing == null)
        {
            session.Store(symbols, "Symbols");
        }
        else if(symbols.Except(existing).Any())
        {
            existing = symbols;

            session.Store(existing, "Symbols");
        }

        session.SaveChanges();

    }

The below code constructs the object fine...

    private async Task GetSymbols(Symbols symbols)
    {
        using var client = new BitfinexClient();

        var data = await client.GetSymbolDetailsAsync();
        foreach (var symbol in data.Data)
        {
            symbols.Add(new Symbol(symbol));
        }
    }

When I use the below, there is no exception... however I want to avoid having to write .Items to access the list:

    internal class Symbols
    {
        internal List<Symbol> Items { get; set; }

        internal Symbols()
        {
            Items = new List<Symbol>();
        }

        internal Symbols(List<Symbol> allSymbols)
        {
            Items = allSymbols;
        }
    }
ayende commented 3 years ago

In JSON, you cannot have an object that is an array. When you inherit from a collection, you are serializing the value as an array, but RavenDB expect documents to be an object. The later document format with the Items is the way to go.