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] NRE in Upsert when writing string[] #1952

Open MichaelRumpler opened 3 years ago

MichaelRumpler commented 3 years ago

Version 5.0.10

Describe the bug I want to write a string[] as BsonValue. The code which worked with LiteDB 4.1.4 throws a NullReferenceException in 5.0.10.

Code to Reproduce

[Fact]
public void Upsert_StringArray()
{
    using (var liteDatabase = new LiteDatabase(new MemoryStream()))
    {
        var items = liteDatabase.GetCollection("items");

        var bdoc = new BsonDocument();
        bdoc["_id"] = Guid.NewGuid();
        bdoc["stringArray"] = new BsonValue(new string[] { "a" });

        var docs = new List<BsonDocument>() { bdoc };
        items.Upsert(docs);
    }
}

Expected behavior The string[] would be written to the database.

Stacktrace

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at LiteDB.BsonValue.GetBytesCount(Boolean recalc)
   at LiteDB.BsonValue.GetBytesCountElement(String key, BsonValue value)
   at LiteDB.BsonDocument.GetBytesCount(Boolean recalc)
   at LiteDB.Engine.DataService.Insert(BsonDocument doc)
   at LiteDB.Engine.LiteEngine.InsertDocument(Snapshot snapshot, BsonDocument doc, BsonAutoId autoId, IndexService indexer, DataService data)
   at LiteDB.Engine.LiteEngine.<>c__DisplayClass31_0.<Upsert>b__0(TransactionService transaction)
   at LiteDB.Engine.LiteEngine.AutoTransaction[T](Func`2 fn)
   at LiteDB.Engine.LiteEngine.Upsert(String collection, IEnumerable`1 docs, BsonAutoId autoId)
   at LiteDB.LiteCollection`1.Upsert(IEnumerable`1 entities)

Additional context In BsonValue.GetBytesCount this.Type is BsonType.Array but this.AsArray returns null.

Maybe I'm doing this wrong. If you tell me how I should convert arrays to BsonValues in a different way, I'd also be fine. Especially if I could write/read different kinds of arrays. Currently I'm limited to string arrays.

lbnascimento commented 3 years ago

@MichaelRumpler The BsonValue constructor should never be used for arrays (and documents). You should use the BsonArray constructor instead. BsonArray inherits from BsonValue, and if you call the BsonValue constructor directly, you'll end up with an instance that is not a BsonArray but which has Type == BsonType.Array. Try using something like this:

var bsonArray = new BsonArray(new BsonValue[] { "a" });
MichaelRumpler commented 3 years ago

I ended up using BsonMapper.Global.Serialize. This seems to work too so far.

Apparently I did something wrong, but it still worked in v4 and breaks in v5. I'll leave this issue open. If you decide that it is ok to throw in v5, you should at least throw a better exception. The NRE tells you nothing.

lbnascimento commented 3 years ago

@MichaelRumpler This worked in LiteDB v4 because the BsonArray class didn't exist yet, so everything was a BsonValue.

Calling BsonMapper.Global.Serialize(...) is probably the best solution right now, as it should work for every class in every situation.

I'll leave this issue open for now, while I think of a way to solve or mitigate this issue.