I am trying to retrieve data from local MongoDb with JSON documents like this:
{
"teryt_num_code" : "AB",
"id": 1,
"name": "Alabama"
}
I have created a POCO class as following:
public class Sensor
{
public int id { get; set; }
public string name { get; set; }
public string teryt_num_code { get; set; }
}
To retrieve all data from my database I use a method below:
public async Task<ICollection<Sensor>> GetAllAsync() => await _collection.Find(_ => true).ToListAsync();
but the exception was thrown calling that method:
An error occurred while deserializing the property of class Sensor:
Element 'id' does not match any field or property of class Sensor.
What I am doing wrong?
By default, the id property of your class is mapped to the BSON id (property _id on the document). This leaves the id property in the MongoDB document without a corresponding property in the POCO. Therefore, the error is raised.
In order to fix the deserialization error, you can apply both a BsonNoId and a BsonIgnoreExtraElements attribute to the POCO:
[BsonNoId]
[BsonIgnoreExtraElements]
public class Sensor
{
public int id { get; set; }
public string name { get; set; }
public string teryt_num_code { get; set; }
}
BsonNoId disables the convention; therefore BSON id is mapped to POCO id. BsonIgnoreExtraElements fixes the error that the driver does not know how to deserialize the _id property that every MongoDB document contains.
Related
I have to work with an API which handles error responses like this:
{
"error": {
"code": 3,
"message": "error message"
}
}
And success respones like this:
{
"data": {
"key": "value"
}
}
Error respones will always contain a code (integer) and a message (string), where as success respones can be different a lot ranging from just a few key-value-pairs to many objects and arrays.
I have created classes for every success "data" section and I can parse them successfully. Now I struggle with the simple part to determine if the response I got is actually an error or a success response.
My Idea was to create these classes:
public class APIResponse
{
[JsonProperty("error")]
public APIResponseError Error { get; set; }
[JsonProperty("data")]
public string Data { get; set; }
}
public class APIResponseError
{
[JsonProperty("code")]
public int Error { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
And to serialize to the class APIResponse. This works only for error responses (kinda obvious) as the data responses are more than just a string which the APIResponse.Data actually is. My idea was to not deserialize the data field and just store it as a string in APIResponse.Data. Then, when I check and see that error is null, I would deserialize the APIResponse.Data property with the correct class. But how can I do this?
You can set type of Data property to JToken:
public class APIResponse
{
[JsonProperty("error")]
public APIResponseError Error { get; set; }
[JsonProperty("data")]
public JToken Data { get; set; }
}
And deserialize later with ToObject:
myCorrectResponse.Data.ToObject<ExpectedDataType>()
But I highly doubt that you will be sent any data in case of error response so I would recommend making APIResponse generic (where T could be object, array, etc.):
public class APIResponse<T>
{
[JsonProperty("error")]
public APIResponseError Error { get; set; }
[JsonProperty("data")]
public T Data { get; set; }
}
Which, in case of your example json will be used for example like:
class MyClass
{
[JsonProperty("key")]
public string Key { get; set; }
}
JsonConvert.DeserializeObject<APIResponse<MyClass>>(json);
I would like to retrieve a nested object from documents in my index called "userprofiles".
My UserProfile model:
public class UserProfileModel
{
public string FullName { get; set; }
public string Oid { get; set; }
public string Upn { get; set; }
public List<SsoLink> FavoriteSsoLinks { get; set; } = new List<SsoLink>();
}
My SsoLink Model:
public class SsoLink
{
public string Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public string Owner { get; set; }
}
Index creation:
PUT userprofiles
{
"mappings" : {
"properties" : {
"FavoriteSsoLinks" : {
"type" : "object"
}
}
}
}
My Query:
var searchResponse = _client.Search<UserProfileModel>(s => s
.Index(_profileIndex)
.Query(q=>q
.Term(t => t.Field(t => t.Oid).Value(oid)
)
)
);
Right now it returns the documents, but the favoritelinks object is blank, however I see objects listed from Kibana. I must be missing something obvious, but having trouble figuring this out.
Here is an example of my data:
Index creation example uses "FavoriteSsoLinks" as the property, but the Kibana screenshot uses "favoriteSsoLinks" starting with a lowercase f - which is correct? My suspicion is that property casing may be the issue, but would need to see the mapping in the index to know if this is correct.
The 7.x client is strict about property name casing in JSON, and by default uses camelcase property names to match to POCO property names. For example, by default
"favoriteSsoLinks" in JSON will be a match for FavoriteSsoLinks POCO property
"FavoriteSsoLinks" in JSON will not be a match for FavoriteSsoLinks POCO property
This behaviour can be changed with DefaultFieldNameInferrer(Func<string, string>) on ConnectionSettings for all properties, or on a property by property basis using attributes on properties such as DataMemberAttribute or PropertyNameAttribute, or using DefaultMappingFor<T> where T is the POCO type.
I'm having big problems when I'm overriding the Id property in a derived class in my MongoDB storage setup.
The base class that all of my data models inherit looks like this:
public abstract class DataModel
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public virtual string Id { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
public DateTime Deleted { get; set; }
}
Then there are a few particular child data models that use upserts. This requires me to annotate the overridden Id property with [BsonIgnoreIfDefault] as per this answer:
public class ChildDataModel : DataModel
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[BsonIgnoreIfDefault] // <---- need this for Upserts to work
public override string Id { get; set; }
... // child-specific properties
}
But unfortunately it results in this error:
The property 'Id' of type
'Namespace.ChildDataModel' cannot
use element name '_id' because it is already being used by property
'Id' of type 'Namespace.DataModel'.
I've tried registering class maps, adding a type discriminator with RootClass = true on the base model, as well as defining my child data models on the base class with the special [BsonKnownTypes(typeof(ChildDataModel), ...)] attribute, but to no avail.
What am I doing wrong here?
MongoDB driver by convention tries to map all the properties named Id into _id in class map. As you have two classes it registers the _id twice. What's more BsonIgnoreIfDefault would work fine if the Id was null but here it's not since the driver automatically will generate the value when you insert new document.
To fix that you can use BsonIgnore if you want to have single _id in MongoDB
public class ChildDataModel : DataModel
{
[BsonRepresentation(BsonType.ObjectId)]
[BsonIgnore]
public override string Id { get; set; }
}
will be stored as:
{
"_id" : ObjectId("5cb5fe72e2a22b3848b6a1f6"),
"Created" : ISODate("2019-04-16T16:10:25.908Z"),
"Modified" : ISODate("2019-04-16T16:10:25.914Z"),
"Deleted" : ISODate("2019-04-16T16:10:25.914Z")
}
or you can use BsonNoId attribute if you want to have two values stored separately:
[BsonNoId]
public class ChildDataModel : DataModel
{
[BsonRepresentation(BsonType.ObjectId)]
public override string Id { get; set; }
}
will be:
{
"_id" : ObjectId("5cb5fecde2a22b3088ef731c"),
"Created" : ISODate("2019-04-16T16:11:56.810Z"),
"Modified" : ISODate("2019-04-16T16:11:56.822Z"),
"Deleted" : ISODate("2019-04-16T16:11:56.822Z"),
"Id" : ObjectId("5cb5fecde2a22b3088ef731c")
}
however it's still the same value from application point of view
I have this Bson model in mongodb:
"setting|resources" : {
"Name" : "EsupHamrah",
"Id" : "449ea0e1-0261-4bee-b096-a838746c94ea",
"Children" : [..
I have created this contract model :
public class SettingResources
{
public string Name { get; set; }
[BsonElement]
//[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
//[BsonId(IdGenerator = typeof(CombGuidGenerator))]
//public string Id { get; set; }
public IList<object> Children { get; set; }
}
When i do query I got tthis error:
An error occurred while deserializing the configs property of class ConsoleMongoApp.Applications: An error occurred while deserializing the values property of class ConsoleMongoApp.Configs: An error occurred while deserializing the resources property of class ConsoleMongoApp.Values: Element 'Id' does not match any field or property of class ConsoleMongoApp.SettingResources.
The Id is just string, not GUID or ObjectId but why mongo can not do map the id?
your model in mongodb is wrong, there is a | (pipe) in Json string, try fix it on "settingResources":
"settingResources" : {
"Name" : "EsupHamrah",
"Id" : "449ea0e1-0261-4bee-b096-a838746c94ea",
"Children" : [..
I have a Dictionary<string, object> as an insertion input for MongoDB.Save().
When I fill this dictionary with primitive types - the serialization process works fine but when I'm trying to insert my dictionary filled with some custom class (for example - Person class) I'm getting the next error:
.NET type 'Person' cannot be mapped to a BsonValue
I noticed that if I insert my custom type converted to BsonDocument the serialization process work great.
How can I define MongoDB to serialize a particular class as BsonDocument?
Update: real code provided
here is my SensorData class:
[DataContract]
public class SensorData
{
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Type { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Desc { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public SensorStatus Status { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Value { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string ValueUnits { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.Boolean)]
public bool Event { get; set; }
[DataMember]
[BsonElement]
[BsonRepresentation(BsonType.Int32)]
public int TTL { get; set; }
}
Here is the methods which saves the data to MongoDB:
public void Save(Dictionary<string, object> RawSensorMessageDictionary)
{
try
{
var policyColl = new MongoClient(ConnString).GetServer().GetDatabase(DB_NAME).GetCollection<BsonDocument>(CollectionNames.RawSensorMessage.ToString());
if (!RawSensorMessageDictionary.Any(p => p.Key == "_id")) //if rawSensorMessageID is empty, mongodb will set it
RawSensorMessageDictionary.Add("_id", ObjectId.GenerateNewId().ToString());
var bsonObj = new BsonDocument(RawSensorMessageDictionary);
policyColl.Save(bsonObj);
}
catch (Exception ex)
{
//log exception
}
}
And here is the code from my UnitTest:
DataAccess.MongoDAL dal = new DataAccess.MongoDAL();
Dictionary<string, object> dic = new Dictionary<string, object>();
dic.Add("1", true);
dic.Add(Message.RESOURCE_TYPE, "aaa");
dic.Add(Message.RESOURCE_NAME, "bbb");
dic.Add(Message.SENSOR_DATA, new SensorData { Desc = "aaa", Event = true, Status = SensorStatus.Active, TTL = 4, Type = "sss", Value = "222" });
dal.SaveRawSensorData(dic );
I want my app to Serialize automatically SensorData object to BsonDocument. If I don't do it manually I'm getting the following exception: .NET type 'SensorData' cannot be mapped to a BsonValue
How should I do it?
Update 2:
OK found the issue, In my "save to db" method I used the next code in order to convert my dictionary BsonDocument:
var bsonObj = new BsonDocument(RawSensorMessageDictionary);
I would expect that creating a new instance of 'BsonDocument' from other object will handle the conversion exactly like object.ToBsonDocument() does but apparently it's not.
Finally I replaced the code above to the following one:
var bsonObj = RawSensorMessageDictionary.ToBsonDocument();
dal.SaveRawSensorData(dic );
now it works.
If the string in the dictionary is the property in your mongodb document, than a non primitive type is a subdocument in mongodb. Please try to mark all properties in the class with [BsonElement] and the Bson Id with [BsonId]. And be sure you follow all rules from here: Serialize Documents with the C# Driver
It would be nice, if you provide some more information
EDIT 1
I would try to remove all properties of SensorData besides Value. If this works, i would than add all other (one by one).