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

Different behaviour using public setters and BsonCtor #1564

Open situssoft opened 4 years ago

situssoft commented 4 years ago

I have a the following data types:

public class InnerItem
{
    public InnerItem(string innerData)
    {
        InnerData = innerData;
    }
    public string InnerData { get; set; }
}

public class DataItem
{
    public DataItem() { }

    public DataItem(string TheData, InnerItem TheItem)
    {
        Id = ObjectId.NewObjectId();
        this.TheData = TheData;
        this.TheItem = TheItem;
    }
    public ObjectId Id { get; set; }
    public string TheData { get; set;  }
    public InnerItem TheItem { get; set; }
}

When I insert a DataItem and recover it from a collection all is well, the following test works.

    private static void TestNonBC(LiteDatabase db)
    {
        db.DropCollection("DataItem");
        var col = db.GetCollection<DataItem>();

        var di = new DataItem("First", new InnerItem("Inner:First"));
        col.Insert(di);

        var x = col.Query().First();
        Debug.Assert(x.TheData == di.TheData, "TeData mismatch");
        Debug.Assert(x.TheItem.InnerData == di.TheItem.InnerData, "InnerData mismatch");
        Debug.Assert(x.Id == di.Id, "Id mismatch");
    }

If, however, I add this class

public class DataItemBC
{
    public DataItemBC(string data, InnerItem item)
    {
        Id = ObjectId.NewObjectId();
        TheData = data;
        TheItem = item;
    }
    [BsonCtor]
    public DataItemBC(ObjectId id, string data, InnerItem item)
    {
        Id = id;
        TheData = data;
        TheItem = item;
    }
    public ObjectId Id { get; }
    public string TheData { get; }
    public InnerItem TheItem { get; }
}

This test fails with an exception - see below.

   private static void TestBC(LiteDatabase db)
    {
        db.DropCollection("DataItemBC");
        var col = db.GetCollection<DataItemBC>();

        var di = new DataItemBC("First", new InnerItem("Inner:First"));
        col.Insert(di);

        var x = col.Query().First();
        Debug.Assert(x.TheData == di.TheData, "TeData mismatch");
        Debug.Assert(x.TheItem.InnerData == di.TheItem.InnerData, "InnerData mismatch");
        Debug.Assert(x.Id == di.Id, "Id mismatch");
    }

When I run this test I see the following exception

LiteDB.LiteException: 'Failed to create instance for type 'LitDbExpt.DataItemBC' from assembly 'LitDbExpt.DataItemBC, LitDbExpt, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. Checks if the class has a public constructor with no parameters.'

This is because the parameter list for the BsonCtor constructor does not match with known types owing to the InnerItem class.

If I add InnerItem using BsonMapper.Global.RegisterType I can make the code work but I have to create my own serilisation code for the type. Any attempt to use the serialisation capabilities of LiteDb results in a stack overflow.

Further, if I add a parameterless constructor then I don't get an exception but the returned object has null properties.

Is this expected bahviour? Am I using the BsonCtor incorrectly?

My expectation would be that using the BsonCtor in this way would provide the same results as the public setters. Further I would expect that where a BsonCtor is specified for a class, that constructor is always used regardless of other constructors being available.

Thanks for any advice

Chris.

lbnascimento commented 4 years ago

@situssoft The constructor annotated by BsonCtor must have only parameters of simple types (this limitation was undocumented until yesterday, so you probably missed it). This is not the case because of the InnerItem parameter.

In this case, the mapper tries to use a parameterless constructor, but there is none. Also, mere adding a parameterless constructor won't work with DataItemBC because he mapper tries to manually set the properties when using a parameterless constructor, which won't work because the properties don't have public setters.

trockefeller-pathway commented 4 years ago

I'm also under the impression that the parameter names have to match the spelling of the public properties exactly for things to work. I've run into issues with BSON constructors when the public property names did not match the BsonCtor parameter names like in your example TheData = data; instead of TheData = theData;