mbdavid / LiteDB

LiteDB - A .NET NoSQL Document Store in a single data file
http://www.litedb.org
MIT License
8.62k stars 1.25k forks source link

[BUG] Error if missing default constructor #2246

Open NongBenz opened 2 years ago

NongBenz commented 2 years ago

Version LiteDB 5.0.12

Describe the bug NullReferenceException if no parameterless constructor for a generic list element

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
   at LiteDB.BsonValue.get_AsDateTime() in D:\code\LiteDB\LiteDB\Document\BsonValue.cs:line 241
   at LiteDB.BsonMapper.<>c.<.ctor>b__37_7(BsonValue bson) in D:\code\LiteDB\LiteDB\Client\Mapper\BsonMapper.cs:line 129
   at LiteDB.BsonMapper.<>c__DisplayClass38_0`1.<RegisterType>b__1(BsonValue b) in D:\code\LiteDB\LiteDB\Client\Mapper\BsonMapper.cs:line 149
   at lambda_method(Closure , BsonDocument )
   at LiteDB.BsonMapper.Deserialize(Type type, BsonValue value) in D:\code\LiteDB\LiteDB\Client\Mapper\BsonMapper.Deserialize.cs:line 198
   at LiteDB.BsonMapper.DeserializeList(Type type, BsonArray value) in D:\code\LiteDB\LiteDB\Client\Mapper\BsonMapper.Deserialize.cs:line 258
   at LiteDB.BsonMapper.Deserialize(Type type, BsonValue value) in D:\code\LiteDB\LiteDB\Client\Mapper\BsonMapper.Deserialize.cs:line 158
   at LiteDB.BsonMapper.DeserializeObject(EntityMapper entity, Object obj, BsonDocument value) in D:\code\LiteDB\LiteDB\Client\Mapper\BsonMapper.Deserialize.cs:line 299
   at LiteDB.BsonMapper.Deserialize(Type type, BsonValue value) in D:\code\LiteDB\LiteDB\Client\Mapper\BsonMapper.Deserialize.cs:line 220
   at LiteDB.LiteQueryable`1.<ToEnumerable>b__27_2(BsonDocument x) in D:\code\LiteDB\LiteDB\Client\Database\LiteQueryable.cs:line 269
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at Program.Main(String[] args) in D:\code\LiteDB\ConsoleTest\Program.cs:line 44

Code to Reproduce

using LiteDB;
using System;
using System.Collections.Generic;
using System.IO;

class DataContainer
{
    public DateTimeOffset Date { get; set; }
    public DataContainer(DateTimeOffset dateTime) => Date = dateTime;

    //public DataContainer() { }
    // Uncomment this for no errors
}

class Player
{
    [BsonId(false)]
    public Guid Id { set; get; }

    public List<DataContainer> Data { set; get; } = new List<DataContainer>();
}

class Program
{
    static void Main(string[] args)
    {
        var player = new Player()
        {
            Id = Guid.NewGuid(),
            Data = new List<DataContainer>
            {
                new DataContainer(DateTimeOffset.Now)
            }
        };

        using (var db = new LiteDatabase(new MemoryStream()))
        {
            var col = db.GetCollection<Player>();

            col.DeleteAll();

            col.Upsert(player);

            foreach (var i in col.FindAll())
            {
                Console.WriteLine(i.Id);
                Console.WriteLine(i.Data.Count);
            }
        }

        Console.ReadLine();
    }
}

Expected behavior Not sure if this is expected behavior, but can it be handled better?

pjy612 commented 2 years ago

https://github.com/mbdavid/LiteDB/issues/2225

JintaoXIAO commented 2 years ago

after going through the source code, you have two choices:

  1. add a default constructor
  2. change the property name in DataContainer: "Date" -> "dateTime", after change, because when constructing the object with its constructor, it will search 'dateTime' in the doc(BsonDocument), but there's no such field because you only persisted a field named "Date". I think this is a defect when not providing a default constructor to re-construct the object from the document, this should be taken as a specification, just like java's serialized specification. LiteDB should not provide a way to construct an object from its params constructor, because it has so much limitations to function

please see BsonMapper.cs#395