Search code examples
c#litedb

LiteDB: Invalid BSON data type 'Null' on field '_id'


Using LiteDB, and it is amazing. It works well for loading and storing data, however, not on subsequent loads after the database has been created.

On initial load, everything is perfect. It creates the database and stores the new record flawlessly, and query returns empty since nothing exists in that collection yet.

On subsequent load, after querying the data (works and gets the result), there is a problem in the .Update() which is causing this issue. According to their documentation, when an 'Id' is not specified it is supposed to create one. When the object is returned from the collection, it does not contain this '_Id' field, and thus fails to update the record in the database.

public class AuctionCache
{
    public double lastModified { get; set; }
    public string server { get; set; }
    public AuctionCache() { }
}

private static bool IsCached(AuctionCache auction)
{
    string filename = string.Format("{0}-{1}.json", auction.server, auction.lastModified);
    bool cached = false;
    try
    {
        using (LiteDatabase db = new LiteDatabase("cache.db"))
        {
            // Get customer collection
            var auctions = db.GetCollection<AuctionCache>("auctions");

            // Use Linq to query documents
            try
            {
                var results = auctions.Find(x => x.server == auction.server).DefaultIfEmpty(null).Single();
                if (results == null)
                {
                    // Insert new cached server
                    auctions.Insert(auction);
                    auctions.EnsureIndex(x => x.server);
                }
                else
                {
                    if (results.lastModified < auction.lastModified)
                    {
                        // Update existing cached server data
                        results.lastModified = auction.lastModified;
                        auctions.Update(results);
                        auctions.EnsureIndex(x => x.server);
                    }
                    else
                    {
                        cached = File.Exists(filename);
                    }
                }
            }
            catch (LiteException le1) {
                Log.Output(le1.Message);
                // Get stack trace for the exception with source file information
                var st = new StackTrace(le1, true);
                // Get the top stack frame
                var frame = st.GetFrame(0);
                // Get the line number from the stack frame
                var line = frame.GetFileLineNumber();
                var module = frame.GetMethod();
                var file = frame.GetFileName();
            }
            catch (Exception e)
            {
                Log.Output(e.Message);
                // Get stack trace for the exception with source file information
                var st = new StackTrace(e, true);
                // Get the top stack frame
                var frame = st.GetFrame(0);
                // Get the line number from the stack frame
                var line = frame.GetFileLineNumber();
            }
        }
    } catch (Exception ee) {
        Log.Output(ee.Message);
        // Get stack trace for the exception with source file information
        var st = new StackTrace(ee, true);
        // Get the top stack frame
        var frame = st.GetFrame(0);
        // Get the line number from the stack frame
        var line = frame.GetFileLineNumber();
    }
    return cached;
}

If you wish to use the above code quickly, you can use the following for demonstration purposes (initial load) :

   AuctionCache ac = new AuctionCache();
   ac.lastModified = (double)12345679;
   ac.server = "Foo";

   // should return true on subsequent loads.
   Console.WriteLine( IsCached( ac ) );

Make sure when testing to stop the program after initial creation, and then start the program 'fresh' for a clean test of loading / updating the database using the following code :

   AuctionCache ac = new AuctionCache();
   ac.lastModified = (double)22345679;
   ac.server = "Foo";

   // should return true on subsequent loads.
   Console.WriteLine( IsCached( ac ) );

This should ensure that it will try to update the record and flag the problem in your IDE as lastModified is newer than the one stored in cache.db which will trigger the .Update method inside my IsCached method.


Solution

  • When you have a object without an identification, LiteDB convert your object to BsonDocument and create a new "_id" on insert. If you query your database (using shell) you can see your document there with an _id (ObjectId).

    But, to update your document, you must use this _id generated on insert (see here: https://github.com/mbdavid/LiteDB/blob/v2.0.0-rc/LiteDB/Core/Collections/Update.cs#L25). Documents without id is useful only when you store this _id in another database (sql) or for insert only.

    In you example, if server is you document id, use [BsonId] attribute to solve or create an public Guid Id { get; set; }