Saving documents with arrayobjects causes an error in db19

Just switched from Couchbase.Lite (.NET) from db014 to db019 and apparently saving documents with arrayobjects now causes an exception.
Here’s what I am trying to do:

public class MEABean{
	...
	public List<MessageBean> Messages { get; set; }
	...
}
	
public class MessageBean {
    public string ContactAddress { get; set; }
    public string Message { get; set; }
    public DateTime CreationDate { get; set; }
    public DateTime ChangeDate { get; set; }
}

//converting the MEABean to a couchbase-document
...
if (bean.Messages != null) doc.Set("Messages", bean.Messages);
...
	
//save
database.Save(doc);

On saving I get the following exception:
System.InvalidCastException occurred
HResult=0x80004002
Message=Cannot encode […]MessageBean to Fleece!
Source= Cannot evaluate the exception source
StackTrace: at LiteCore.Interop.FLSliceExtensions.FLEncode(Object obj, FLEncoder* enc)

This exception is not thrown with version db014 (no info about versions db015-db018).

When using a simple type instead of a class, i.e. List of string instead of List of MessageBean: everything is fine.
As soon as a class (apparently any class) is used, I get the above error.

Any ideas? Any checks I could do in order to narrow it down?

Saving your own custom classes into a document is not supported. Working before was just luck. We will bring the model class to the library in a future release but for now you need to convert your messages to dictionaries first.

Thanks for clarifying, got it!
I will be looking forward to your model classes and in the meantime the problem is solved by converting my classes to dictionaries as you suggested.
Here’s the above example for completeness:

public class MEABean{
   ...
   public List<MessageBean> Messages { get; set; }
   ...
}
	
public class MessageBean {
    public string ContactAddress { get; set; }
    public string Message { get; set; }
    public DateTime CreationDate { get; set; }
    public DateTime ChangeDate { get; set; }
	
	
    public Dictionary<string, object> toCBDictionary() {
       Dictionary<string, object> messageDict = new Dictionary<string, object>();
       messageDict.Add("ContactAddress", this.ContactAddress);
       messageDict.Add("Message", this.Message);
       messageDict.Add("CreationDate", new DateTimeOffset(this.CreationDate));
       messageDict.Add("ChangeDate", new DateTimeOffset(this.ChangeDate));
       return messageDict;
   }
}

//converting the MEABean to a couchbase-document
...
if (!bean.Messages.IsNullOrEmpty()) {
    List<Dictionary<string, object>> messagesList = bean.Messages.Select(x => x.toCBDictionary()).ToList();
    doc.Set("Messages", messagesList);
}
...
	
//save
database.Save(doc);			

Thanks for your help!

Hi,

Thank you for sharing your solution.

How do you do after a query to put back datas from your dictionnary to your MessageBean object ?

Regards,

Steeve

By hand, really :slightly_smiling_face:
That’s why we are indeed looking forward to the model class implementation. But for the moment we are ok using straightforward procedures which handle the conversion from class to doc and back.
Here’s the requested code for the reverse direction:

//reading the couchbase-document representing our MEABean
...
if (!doc.GetArray("Messages").IsNullOrEmpty()) bean.Messages = FillMessages(doc.GetArray("Messages"));
...

public static List<MessageBean> FillMessages(IReadOnlyArray messages)
      {
         List<MessageBean> res = null;
         if (!messages.IsNullOrEmpty())
         {
            MessageBean cmb;
            res = new List<MessageBean>();

            for (int i = 0; i < messages.Count; i++)
            {
               IReadOnlyDictionary message = messages.GetDictionary(i);
               cmb = new MessageBean();
               Fill(message, cmb);
               res.Add(cmb);

            }
         }
         return res;
      }

public static void Fill(IReadOnlyDictionary doc, MessageBean bean)
      {
         bean.ContactAddress = doc.GetString("ContactAddress");
         bean.Message = doc.GetString("Message");
         bean.CreationDate = doc.GetDate("CreationDate").DateTime;
         bean.ChangeDate = doc.GetDate("ChangeDate").DateTime;
      }

Naturally I simplified the code for the entire example to make it easier to read in this post but that’s the gist of it.
Hope it helps.

Hi Claudia,

Thank you, yes it help to have some real use case from real users !

Fragments in doc are great to try to understand, but it’s so hard to make them work togethers.

There are some comportement that I expected in reading the documentation that I can’t find in the code, it’s so frustrating, for example couchbase store pairs of key/values, If I store a json string with a docuement key, I expect to be able to get back my json string when I get the document from his key, thru a simple “Value” property, or at least a function. It may be slower, It could exists a better or more efficient way to do it, but it’s what I expect to have as developper. As a first step to make it worked and be ready to learn the more efficient way.

I will continue to try to make it usefull.

Regards,

Steeve

Could you be more specific about what you think is lacking (besides a way to use your own custom classes)? You mention a simple “Value” method, but with the fragment API you could do something like doc["nested"]["key"]["path"].ToString() to get the string value for nested.key.path. Or if it contains an int, use ToInt(), etc. For 2.1 or later we plan to introduce a way to map documents to your own custom classes (it’s not quite as simple as just filling in properties, etc, because we have to account for changes in the data structure and make lazy loading possible, etc). I was sad to see it go from the 2.0 release plan, but it had to be sacrificed to keep the amount of work (accouting for Java, Swift, and C#) vs amount of resources on our side reasonable.

Hi,

Starting with this context in couchbaseLite, (c#):

        Document document = new Document("airline_99999");
        Airline airline = new Airline();
        airline.Type = "airline";
        airline.Id = 99999;
        airline.Name = "Steeve";
        airline.Country = "France";
        string jsonAirline = airline.ToJson();

        document.Set("airline_99999", jsonAirline );
        myCouchbaseLiteDataBase.Save(document); 

I expect to be able to do so:
string jsonBack = myCouchbaseLiteDataBase.GetDocument("airline_99999").Value;
Or
string jsonBack = myCouchbaseLiteDataBase.GetDocument("airline_99999").Value.ToString();
OR
string jsonBack = myCouchbaseLiteDataBase.GetDocument("airline_99999").ToJson();

As simple as that, my key is airline_99999, my value is my json string.

When I’m doing a Query, I expect to be able to do :

var query = Query.Select()
        .From(DataSource.Database(myCouchbaseLiteDataBase));

        IResultSet rows = query.Run();

        foreach (IResult row in rows)
        {
           string jsonBack = row.ToJson(); // Or Value or Value.ToString()
        }

It’s seem to be very simple way to start with couchbase.

With the user point of view, I have my object that I want to store (persist) and share with the server. I store a Json, Couchbase server shows that It does the same, in conclusion I expect to by able to have back my json easely, In the Key/value way.

I understand a query can do much more, but as a developper, I expect to have firsly the logical and simple result, and think about the rest after that. The best friendly way for users like me.

Hope this could help.

Regards,

Steeve

I would recommend that you take a look at the blog post that introduces the Query interface in 2.0 so you have a better idea of what is possible and how to parse the results. It starts with the very simple case which is what you are looking for.
While examples are in swift, it should be very straightforward to map them to c#- the query builder API is similar across the platforms.

What you’re actually asking here is to be able to store custom objects, like Airline, in a document. Using JSON is just a workaround for that. I know you’re very accustomed to using JSON strings to store information, but when you do that the data inside the string stops having meaning to the database and can’t be queried conveniently.

Storing custom objects is a feature that unfortunately slipped out of 2.0. We hope to add it in one of the next follow-on releases.

As simple as that, my key is airline_99999, my value is my json string.

Sure, that will work.

It isn’t really “simple”, though, because you’re going through this extra step of manually encoding/decoding JSON. And once you’ve got the object data encoded into a string, it’s not queryable anymore because it’s not individual properties.

Hi,

Thank you @priya.rajagopal, Your article is great !

I think I will use it as a reference for a while.

I’d like to add that, in a certain point of view, you confirme the need of a ToJson() function. Let me explain thru a question :
How do you do to explain the result of queries for your reader to understand ?

By showing a json ! (Tadaa (horns and trumpets :smile:)
Readers of your article are Developper(at least a part of), so for them to understand the results of theirs queries, they could use this function toJson() I recommended answering Jim @borrrden question.

Perhaps I say so because I’m a beginner with couchbase, and my opinion will change after a long journey hand by hand with it, But beginner like me will be your best advocates if they can make couchbase worked, and find it easy to implement.

Make yourself know nothing about couchbase for a moment, think about how a beginner will think and expect starting with couchbase, and add the necessary functions et docs to make it work (as you did with your article), even if this way is slower than the way you recommended, if it could help to be happy to start with couchbase, I think it worth it, isn’t it ?

This is just a point of view I’m sharing with you, I’m not so sure I’m right, I’m just saying what I expect as a beginner.

Thanks again for your helps,

Regards,

Steeve

Glad you liked the blog post. There will be follow up posts that talk about advanced querying capabilities which are pretty cool !

In the blog post, you can see sample responses in the “Processing Query Responses” section. And of course, you can run the playground to see the responses for any of the queries .

As you can tell from the section above in the blog, the responses are in JSON. It is specified as an array/list of Map/Dictionary objects (which is how you would serialize/deserialize JSON to platform native types). You have type setters and getters for set and get every member of the JSON.

If you have a “JSON string”, you will encode it to a top level dictionary/ array and store it. If you store the JSON as a string, then you will get back a string. What you set is what you get.

As @borrrden mentioned, we don’t have support for custom types in 2.0 but that’s coming in 2.1…that will allow you the encode/decode JSON to any custom native type.