Unable to retrieve subdocument items in mongodb c# - c#

Model structure:
//Main doc model
Public class MainDocumentModel
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Name{ get; set; }
public List<SubDocuementModel> SubDocsList { get; set; }
}
//sub doc model
Public class SubDocumentModel
{
public string Id { get; set; }
[BsonIgnoreIfNull]
public string MainDocId { get; set; }
public string Name{ get; set; }
[BsonIgnoreIfNull]
public List<SubDocuementModel> SubDocsList { get; set; }
.
.
.
}
In DB when I insert MainDocuement, its structure looks like:
{
"_id" : ObjectId("54b9d732bb63bc10ec9bad6f"),
"Name" : "Name-1",
"SubDocsList : [{
"Id" : "54b9e76dbb63bc1890e8761b",
"Name" : "Sub-Name-1"
},....]
}
Now I want to retrieve subdocuments where main doc Id == 54b9d732bb63bc10ec9bad6f:
private void getSubDoc(SubDocumentModel oModel)
{
_MainDoc = _db.GetCollection<MainDocumentModel>("MainDoc");
_query = Query<MainDocumentModel>.Where(e => e.Id == oModel.MainDocId);
//Here I get the exception
//Exception: An error occurred while deserializing the SubDocsList property
of class Data.MainDocumentModel: Element 'Id' does not match any field or
property of class Data.SubDocumentModel.
private MainDocumentModel _mainDocModel = _MainDoc.FindOneAs<MainDocumentModel>(_query);
oModel.SubDocsList =
_mainDocModel .SubDocsList .FindAll(
x => x.Name.ToLower().Contains("NameFilter"));
//Pagination
oModel.SubDocsList =
oModel.SubDocsList .Take(oModel.ItemsPerPage)
.Skip(oModel.ItemsPerPage*(oModel.PageNo - 1))
.ToList();
}
Why I am getting above deserializing exception. What I am doing wrong?
Here I am applying the pagination logic to the retrieved c# list. How I can apply pagination itself in mongo query?
It's very easy to apply pagination for main doc as:
mainDocCursor = MainDoc .Find(_query).SetSortOrder(SortBy.Ascending("Name")).SetLimit(oModel.ItemsPerPage).SetSkip(oModel.ItemsPerPage * (oModel.PageNo - 1));
How can I achieve the same pagination for subdocuments?

I solved the exception of deserializing, with following small changes:
In SubDocumentModel:
Public class SubDocumentModel
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
.
.
.
}
And when I insert the subdocument, I am saving the Id as object id not as a string (previously I was saving it as a string which was causing the problem):
public void addSubDocument(SubDocumentModeloModel)
{
_MainDoc = _db.GetCollection<MainDocumentModel>("MainDoc");
BsonDocument subDocument = new BsonDocument
{
{"_id", ObjectId.GenerateNewId()},//Previously it was {"Id", ObjectId.GenerateNewId().ToString()}
{"Type", oModel.Name}
};
_query = Query<MainDocumentModel>.Where(e => e.Id == oModel.MainDocId);
_updateBuilder = Update.Push("SubDocsList", subDocument);
_MainDoc.Update(_query, _updateBuilder);
}
However I've still not found a way for pagination of subdocument in mongodb-c#

Related

LINQ query to push an item to the array of a Mongo Document

I want to push a ProductPhoto inside the Product using LINQ. Here is my model:
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public string Detail { get; set; }
public List<ProductPhoto> ProductPhotos { get; set; }
}
public class ProductPhoto
{
public string Id { get; set; }
public string Url { get; set; }
}
How can I achieve this with the LINQ query? I am expecting the result to be something like this.
{
_id: ObjectId('602d2149e773f2a3990b47f5'),
Name: 'Some name',
Detail: 'Some description',
ProductPhotos: [
{ _id: ObjectId('602d2149e773f2a3990b47f5'), Url: 'Some URL' }
]
}
Next time when I add a new ProductPhoto, then there will be two elements.
Thanks!
Here is the solution to similar question:
How to 'Find And Push' element in an array inside of another array in a document using LINQ to MongoDb
You can achieve this with something like this:
var filterBuilder = Builders<Product>.Filter;
var filter = filterBuilder.Eq(x => x.Id, productId);
var updateBuilder = Builders<Product>.Update;
var update = updateBuilder.Push(doc => doc.ProductPhotos, productPhoto);
Products.UpdateOneAsync(filter, update);

LINQ to JSON - Newtonsoft.Json.Linq.JProperty Error

The following is my json string:
string json = #"{
'?xml' : {
'#version' : '1.0',
'#encoding' : 'UTF-8'
},
'DataFeed' : {
'#FeedName' : 'AdminData',
'Issuer' : {
'id' : '95',
'name' : 'Apple',
'symbol' : 'AAPL'
}
}
}";
When I try to do the following LINQ query:
JObject feed = JObject.Parse(json);
var compInfo = feed["DataFeed"]["Issuer"]
.Select(c => c["name"]);
I get the following error:
`Cannot access child value on Newtonsoft.Json.Linq.JProperty.`
However, the following works fine:
var test1 = feed["DataFeed"]["Issuer"]["name"];
Any idea why I can't use LINQ on this json string?
Think about what your JSON is. You're selecting from a dictionary so the result in the LINQ is the property. You're trying to then access "name" on a property which doesn't make sense which gives you the error.
You already have the working code:
var test1 = feed["DataFeed"]["Issuer"]["name"];
You can get the value you want using two methods:
Method 1:
First you need a cast from JToken to a JObject since the value of 'Issuer' is an object:
var compInfo = (JObject)feed["DataFeed"]["Issuer"];
Then loop through all the properties to find the one with the name "Name" then get its value as a string:
var str = compInfo.Properties().First(x => x.Name == "name").ToObject<string>();
// str will contain the value 'Apple'.
Method 2:
You can also deserialize the JSON into an object that is easier to handle. To do that first you'll need to create a .net object "equivalent" of your JSON . You can use Visual Studio to generate these for you from the Edit menu -> Paste Special -> Paste JSON as classes or use a website like JsonUtils.com
public class Xml
{
[JsonProperty("#version")]
public string Version { get; set; }
[JsonProperty("#encoding")]
public string Encoding { get; set; }
}
public class Issuer
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("symbol")]
public string Symbol { get; set; }
}
public class DataFeed
{
[JsonProperty("#FeedName")]
public string FeedName { get; set; }
[JsonProperty("Issuer")]
public Issuer Issuer { get; set; }
}
public class RootJsonObject
{
[JsonProperty("?xml")]
public Xml Xml { get; set; }
[JsonProperty("DataFeed")]
public DataFeed DataFeed { get; set; }
}
Then all you have to do to get the Issuer name is this:
var feed = JsonConvert.DeserializeObject<RootJsonObject>(json);
var issuer = feed.DataFeed.Issuer.Name;

Locating a sub-object in MongoDB collection using LINQ/C#

I am attempting to write a code that finds objects in MongoDB collection with Linq.
Here's my code:
class Program
{
static void Main(string[] args)
{
var client = new MongoClient();
var db = client.GetDatabase("SoundsDB");
var collection = db.GetCollection<Sound>("SoundsCollection");
string myID = "0vvyXSoSHI";
var myObjects = collection
.Find(b => b.objectId == myID);
}
}
public class Sound
{
public string _id { get; set; }
public Result[] results { get; set; }
}
public class Result
{
public Audio_File audio_file { get; set; }
public DateTime createdAt { get; set; }
public string location { get; set; }
public string objectId { get; set; }
public DateTime updatedAt { get; set; }
}
public class Audio_File
{
public string __type { get; set; }
public string name { get; set; }
public string url { get; set; }
}
Here's the JSON in my MongoDB collection:
{
"_id" : ObjectId("56acced71b8ac8702446e8c6"),
"results" : [
{
"audio_file" : {
"__type" : "File",
"name" : "tfss-3c489351-0338-4903-8d94-a0f0c7091ef9-hi.m4a",
"url" : "http://files.parsetfss.com/hithere.m4a"
},
"createdAt" : "2014-12-27T22:59:04.349Z",
"location" : "Home",
"objectId" : "0vvyXSoSHI",
"updatedAt" : "2015-02-26T22:48:02.264Z"
}
]
}
I am trying to make it work but in the following line:
.Find(b => b.objectId == myID)
I get this error:
Cannot convert lambda expression to type 'MongoDB.Driver.FilterDefinition' because it is not a delegate type
Any idea how can I fix it and be able to search through the JSON for objects using their objectId?
Thanks!
I think the problem is that you are searching for a sub-document, not the main doc. Try this:
var myObjects = collection
.Find(b => b.results.Any(r=>r.objectId == myID));
Also - make sure that the objectId value is actually a string in your collection. It seems like it's a string in the object model but an objectId in the db. You may need to (a) change your object model and (b) change that query so that you are asking for r.objectId == ObjectId.Parse(myID) instead of the way I wrote it.
c# MongoDb .Find is Async
If you're using the c# drivers, you probably also need to implement async for this call:
static void Main() {
MainAsync().Wait();
}
static async Task MainAsync() {
var client = new MongoClient();
var db = client.GetDatabase("SoundsDB");
var collection = db.GetCollection<Sound>("SoundsCollection");
string myID = "0vvyXSoSHI";
var myObjects = await collection
.Find(b => b.objectId == myID).ToListAsync();
}
This way, you are using find, and converting the results to a list (so, myObjects will be a List<SoundsCollection> object).

MongoDB Unable to determine the serialization information for the expression error

My data is having following structure
public enum ParamType
{
Integer=1,
String=2,
Boolean=3,
Double=4
}
public class Gateway
{
public int _id { get; set; }
public string SerialNumber { get; set; }
public List<Device> Devices { get; set; }
}
public class Device
{
public string DeviceName { get; set; }
public List<Parameter> Parameters { get; set; }
}
public class Parameter
{
public string ParamName { get; set; }
public ParamType ParamType { get; set; }
public string Value { get; set; }
}
I filled 10 document objects of Gateway in a MongoDB database.
Now I want to query all those gateways which contains a device having Parameter with ParamName as "Target Temperature" and whose Value > 15.
I created following queries
var parameterQuery = Query.And(Query<Parameter>.EQ(p => p.ParamName, "Target Temperature"), Query<Parameter>.GT(p => int.Parse(p.Value), 15));
var deviceQuery = Query<Device>.ElemMatch(d => d.Parameters, builder => parameterQuery);
var finalQuery = Query<Gateway>.ElemMatch(g => g.Devices, builder => deviceQuery);
But when I run this, it is giving an exception
Unable to determine the serialization information for the expression: (Parameter p) => Int32.Parse(p.Value)
Please suggest where I am wrong.
As the error suggests, you can't use Int32.Parse inside your query. This lambda expression is used to get out the name of the property and it doesn't understand what Int32.Parse is.
If you are querying a string, you need to use a string value for comparison:
var parameterQuery = Query.And(Query<Parameter>.EQ(p => p.ParamName, "Target Temperature"), Query<Parameter>.GT(p => p.Value, "15"));
However, that's probably not what you want to do since you're using GT. To be treated as a number for this comparison you need the value to actually be an int in mongo, so you would need to change the type of your property:
public class Parameter
{
public string ParamName { get; set; }
public ParamType ParamType { get; set; }
public int Value { get; set; }
}
var parameterQuery = Query.And(Query<Parameter>.EQ(p => p.ParamName, "Target Temperature"), Query<Parameter>.GT(p => p.Value, 15));
Summary
I ran into this when I was modifying a list. It appears that Linq First/FirstOrDefault was not handled well by MongoDB for me. I changed to be an array index var update = Builders<Movie>.Update.Set(movie => movie.Movies[0].MovieName, "Star Wars: A New Hope"); Note: This is in Asp.Net 5 using MongoDB.Driver 2.2.0.
Full Example
public static void TypedUpdateExample() {
var client = new MongoClient("mongodb://localhost:27017");
var database = client.GetDatabase("test");
var collection = database.GetCollection<Movie>("samples");
//Create some sample data
var movies = new Movie {
Name = "TJ",
Movies = new List<MovieData>
{
new MovieData {
MovieName = "Star Wars: The force awakens"
}
}
};
collection.InsertOne(movies);
//create a filter to retreive the sample data
var filter = Builders<Movie>.Filter.Eq("_id", movies.Id);
//var update = Builders<Movie>.Update.Set("name", "A Different Name");
//TODO:LP:TSTUDE:Check for empty movies
var update = Builders<Movie>.Update.Set(movie => movie.Movies[0].MovieName, "Star Wars: A New Hope");
collection.UpdateOne(filter, update);
}
public class Movie {
[BsonId]
public ObjectId Id { get; set; }
public string Name { get; set; }
public List<MovieData> Movies { get; set; }
}
public class MovieData {
[BsonId]
public ObjectId Id { get; set; }
public string MovieName { get; set; }
}

MongoDB: automatically generated IDs are zeroes

I'm using MongoDB and official C# driver 0.9
I'm just checking how embedding simple documents works.
There are 2 easy classes:
public class User
{
public ObjectId _id { get; set; }
public string Name { get; set; }
public IEnumerable<Address> Addresses { get;set; }
}
public class Address
{
public ObjectId _id { get; set; }
public string Street { get; set; }
public string House { get; set; }
}
I create a new user:
var user = new User
{
Name = "Sam",
Addresses = (new Address[] { new Address { House = "BIGHOUSE", Street = "BIGSTREET" } })
};
collection.Insert(user.ToBsonDocument());
The user is successfully saved, so is his address.
After typing
db.users.find()
in MongoDB shell, I got the following result:
{ "_id" : ObjectId("4e572f2a3a6c471d3868b81d"), "Name" : "Sam", "Addresses" : [
{
"_id" : ObjectId("000000000000000000000000"),
"Street" : "BIGSTREET",
"House" : "BIGHOUSE"
}
] }
Why is address' object id 0?
Doing queries with the address works though:
collection.FindOne(Query.EQ("Addresses.Street", streetName));
It returns the user "Sam".
It's not so much a bug as a case of unmet expectations. Only the top level _id is automatically assigned a value. Any embedded _ids should be assigned values by the client code (use ObjectId.GenerateNewId). It's also possible that you don't even need an ObjectId in the Address class (what is the purpose of it?).
Use BsonId attribute:
public class Address
{
[BsonId]
public string _id { get; set; }
public string Street { get; set; }
public string House { get; set; }
}
Identifying the Id field or property
To identify which field or property of
a class is the Id you can write:
public class MyClass {
[BsonId]
public string SomeProperty { get; set; }
}
Driver Tutorial
Edit
It's actually not working. I will check later why.
If you need get it work use following:
[Test]
public void Test()
{
var collection = Read.Database.GetCollection("test");
var user = new User
{
Name = "Sam",
Addresses = (new Address[] { new Address { House = "BIGHOUSE", Street = "BIGSTREET", _id = ObjectId.GenerateNewId().ToString() } })
};
collection.Insert(user.ToBsonDocument());
}
Get the collection as User type:
var collection = db.GetCollection<User>("users");
Initialize the field _id as follows:
var user = new User
{
_id = ObjectId.Empty,
Name = "Sam",
Addresses = (new Address[] { new Address { House = "BIGHOUSE", Street = "BIGSTREET" } })
};
Then you insert the object:
collection.InsertOne(user);
The _id field will automatically be generated.
In this link you will find alternative ways to have customized auto-generated ID(s).

Categories