Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

litedb

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.

like image 954
Kraang Prime Avatar asked Jul 13 '16 01:07

Kraang Prime


3 Answers

For others who might have this problem, here is an example that worked for me. I have a product class with a field columnId and I added another Id with type objectId to get rid of that Invalid BSON data type 'Null' on field '_id' error.

public class Product
    {
        [BsonId]
        public ObjectId Id { get; set; }
        public int ColumnId { get; }
        public int Quantity { get; private set; }
     .....

     }

My update method, created in another class is:

  public void UpdateQuantity(Product product)
        {
       var collection = database.GetCollection<Product>("products");
       var databaseProduct = collection.FindOne(x =>x.ColumnId.Equals(product.ColumnId));
       databaseProduct.DecrementQuantity();
       collection.Update(databaseProduct);
        }

Type Guid did not work for me.

like image 58
Andreea Purta Avatar answered Nov 13 '22 06:11

Andreea Purta


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; }

like image 19
mbdavid Avatar answered Nov 13 '22 07:11

mbdavid


I agree with @mbdavid's answer. However, when it's for a type you don't own, like something you're using from a 3rd-party assembly, you'll like need to use the BsonMapper:

BsonMapper.Global.Entity<IdentityServer4.Models.IdentityResources.OpenId>()
    .Id(oid => oid.Name);

Put this somewhere in your startup code.

like image 1
Greg Vogel Avatar answered Nov 13 '22 08:11

Greg Vogel