I have this c# object:
public class Stats
{
[BsonElement("Miss")]
public uint Miss { get; set; }
[BsonElement("Success")]
public uint Success { get; set; }
[BsonElement("Failed")]
public uint Failed { get; set; }
}
and the mongodb doc
{
"_id": {
"$oid": "63c5f18ea66843e308557658"
},
"PlayerId": 10000,
"Miss": 10,
"Success": 20,
"Failed": 5
}
For now to incremnt all I use this:
var filter = Builders<BsonDocument>.Filter.Eq("PlayerId", playerId);
var update = new BsonDocument("$inc", new BsonDocument {
{ "Miss", Stats.Miss },
{ "Success", Stats.Success },
{ "Failed", Stats.Failed }
});
var doc = Collection.FindOneAndUpdateAsync(filter, update).Result;
Is there any other way simplified, as the actual doc has many more fields ?
Mention: PlayerId field to be excluded. I can also move it to _id if it is easier. All the remaining fields are int.
Thank you.
As you have constructed the class just for the properties to be updated, you could use .ToBsonDocument() to convert an object to BsonDocument which is simpler than working with System.Reflection.
BsonDocument updateFields = stats.ToBsonDocument();
var update = new BsonDocument("$inc", updateFields);
Demo
Related
I need to perform group-by with the $max operator in MongoDB. I figure out the query which is working in the MongoDB database but was not able to write the same using C#.
db.getCollection('Employee').aggregate(
[
{$unwind : "$Projects"},
{
"$group" : {
"_id" : "$EmpId",
"LastUpdated" : {"$max" : "$Projects.LastUpdated"}
}
}
]);
Below C# code is giving an error:
"Projects" is not valid property.
_db.GetCollection<BsonDocument>(collection).Aggregate().Unwind(i=>i.Projects)
Assume this is your sample data:
[{
"EmpId": 1,
"Projects": [
{
"LastUpdated": {
"$date": "2021-10-22T16:00:00Z"
}
},
{
"LastUpdated": {
"$date": "2021-11-07T16:00:00Z"
}
},
{
"LastUpdated": {
"$date": "2022-01-22T16:00:00Z"
}
}
]
}]
and this is your model class:
public class Employee
{
public int EmpId { get; set; }
public List<EmployeeProject> Projects { get; set; }
}
public class EmployeeProject
{
public DateTime UpdatedDate { get; set; }
}
To use the Projects property, you need to specify your collection as Project type as:
_db.GetCollection<Employee>("Employee")
Solution 1: Mix use of AggregateFluent and BsonDocument
var result = _db.GetCollection<Employee>("Employee")
.Aggregate()
.Unwind(i => i.Projects)
.Group(new BsonDocument
{
{ "_id", "$EmpId" },
{ "LastUpdated", new BsonDocument("$max", "$Projects.LastUpdated") }
})
.ToList();
Solution 2: Full use of AggregateFluent
Pre-requisites:
Need to create a model for unwinded Project.
public class UnwindEmployeeProject
{
public int EmpId { get; set; }
public EmployeeProject Projects { get; set; }
}
var result = _db.GetCollection<Employee>("Employee")
.Aggregate()
.Unwind<Employee, UnwindEmployeeProject>(i => i.Projects)
.Group(
k => k.EmpId,
g => new
{
EmpId = g.Key,
LastUpdated = g.Max(x => x.Projects.LastUpdated)
})
.ToList();
Solution 3: Full use of BsonDocument
With Mongo Compass, you can export your query to C#.
PipelineDefinition<Employee, BsonDocument> pipeline = new BsonDocument[]
{
new BsonDocument("$unwind", "$Projects"),
new BsonDocument("$group",
new BsonDocument
{
{ "_id", "$EmpId" },
{ "LastUpdated",
new BsonDocument("$max", "$Projects.LastUpdated") }
})
};
var result = _db.GetCollection<Employee>("Employee")
.Aggregate(pipeline)
.ToList();
Output
I want to update the single document in collection with the guid as filter and update value is cityType. Every guid has different citytype here i have used 3 types it may be more.
So please give a right implementation using c# code.
Models:
public class Country
{
[BsonId]
public ObjectId Id { get; set; }
public int CountryId {get; set; }
public IEnumerable<States> States { get; set; }
}
public class States
{
public Guid Guid { get; set; }
public CityType CityType { get; set; }
}
Enum CityType
{
Unknown = 0,
Rural = 1,
Urban = 2
}
Existing Collection:
{
"_id": ObjectId("6903ea4d2df0c5659334e763"),
"CountryId": 200,
"States": [
{
"Guid": "AFCC4BE7-7585-5E46-A639-52F0537895D8",
"CityType": 0,
},
{
"Guid": "208FB603-08C7-46D9-B0C0-7AF4F691A96D",
"CityType": 0,
}
}
Input:
List<States>()
{
new States()
{
Guid = "AFCC4BE7-7585-5E46-A639-52F0537895D8",
CityType = CityType.Rural
},
new States()
{
Guid = "208FB603-08C7-46D9-B0C0-7AF4F691A96D",
CityType = CityType.Urban
}
}
Expected:
{
"_id": ObjectId("6903ea4d2df0c5659334e763"),
"CountryId": 200,
"States": [
{
"Guid": "AFCC4BE7-7585-5E46-A639-52F0537895D8",
"CityType": 1,
},
{
"Guid": "208FB603-08C7-46D9-B0C0-7AF4F691A96D",
"CityType": 2,
}
}
This is the method I have tried:
public async Task<bool> UpdateType(int countryId, IEnumerable<States> states)
{
var collection = connectionFactory.GetCollection<Country>(collectionName);
var cityTypes = states.Select(x => x.CityType);
var filter = Builders<Country>.Filter.Empty;
var update = Builders<Country>.Update.Set("States.$[edit].CityType", cityTypes);
var arrayFilters = new List<ArrayFilterDefinition>();
foreach (var state in states)
{
ArrayFilterDefinition<Country> optionsFilter = new BsonDocument("state.Guid", new BsonDocument("$eq", state.Guid));
arrayFilters.Add(optionsFilter);
}
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
var result = await collection.UpdateOneAsync(filter, update, updateOptions);
return result;
}
hope all details I have added here. Thanks in advance.
You don't have to loop through it:
Let's say you have a Class1 like this:
class Question : AuditableEntity {
public string Text { get; set; }
public List<string> Tags { get; set; } = new List<string>();
so you just say:
await collection.UpdateOneAsync(
someFilter,
Builders<Class1>.Update
.Set(f => f.Text, request.Question.Text)
.Set(f => f.Tags, request.Question.Tags));
I have the following model of the collection "evaluations":
[BsonCollection("evaluations")]
public class Evaluation : Document
{
[BsonRepresentation(BsonType.ObjectId)]
public string AlertId { get; set; }
public string EvaluationStatus { get; set; }
public DateTime EvaluatedAt { get; set; }
}
What I would like to do is to retrieve a list with the last evaluation of each Alert object, which would mean grouping evaluations by AlertId then getting the latest EvaluatedAt of the group, and then return those evaluations.
With the result being a list of 'Evaluation' objects, (one evaluation per AlertId)
How would I construct that query using MongoDB driver with Aggregation?
Thanks for the help!
With a little research came up with this solution:
var alertIds = alertss.Select(x => x.Id.ToString()).ToList();
var sortByLatest = Builders<Evaluation>.Sort.Descending(x => x.EvaluatedAt);
var groupByAlertId = new BsonDocument
{
{ "_id", "$AlertId" },
{ "EvaluationId", new BsonDocument { { "$first", "$_id" } } },
{ "EvaluationStatus", new BsonDocument { { "$first", "$EvaluationStatus" } } },
{ "EvaluatedAt", new BsonDocument { { "$first", "$EvaluatedAt" } } },
};
ProjectionDefinition<BsonDocument> projection = new BsonDocument
{
{"_id", "$EvaluationId"},
{"AlertId", "$_id"},
{"EvaluationStatus", "$EvaluationStatus"},
{"EvaluatedAt", "$EvaluatedAt"},
};
var evaluations = evaluationsRepository.GetCollection().Aggregate()
.Match(x => alertIds.Contains(x.AlertId))
.Sort(sortByLatest)
.Group(groupByAlertId)
.Project(projection)
.As<Evaluation>().ToList();
return evaluations;
(Just a heads up, I'm very new to C#)
(See sample code and JSON structure below)
I can't figure out how to pull "data" out of the JSON reponse and put it into a data table. The variable "response" is just raw JSON data. So far I've figured out how to parse the JSON into a JObject...so now it has two members (data, meta). Now I'm trying to figure out how to get joTest["data"] into a DataTable. The handful of attempts I've made, keep giving me an error when it sees the "meta" member. Maybe I shouldn't be using a Data Table?
Also, in case it changes anything, I don't need the "links" from the "data" members.
I've tried searching for "Converting JObject into Data Table" But I'm not finding a lot of useful results.
public void PerformFeed()
{
string response;
response = Blah.SendMessage().Result;
JObject joTest = JsonConvert.DeserializeObject<JObject>(response);
}
Json Data Structure
{
"data": [
{
"Val1": "1234",
"Val2": "foo1",
"Val3": "bar1",
"links": [
{
"rel": "self",
"uri": "/blah/1234"
},
{
"rel": "pricing_data",
"uri": "/blah/1234/pricing_data"
}
]
},
{
"Val1": "5678",
"Val2": "foo2",
"Val3": "bar2",
"links": [
{
"rel": "self",
"uri": "/blah/5678"
},
{
"rel": "pricing_data",
"uri": "/blah/5678/pricing_data"
}
]
}
],
"meta": {
"pagination": {
"total": 2,
"count": 2,
"per_page": 25,
"current_page": 1,
"total_pages": 1,
"links": []
}
}
}
UPDATE: I've figured out a "solution" but I really don't think it's a good solution. I built a datatable and then used a foreach statement on the JObject to populate the data table that way. It seems very inefficient...but for now it works. Hopefully I'll find a better way.
public void PerformFeed()
{
DataTable Items = new DataTable();
Items.Columns.Add("Val1");
Items.Columns.Add("Val2");
Items.Columns.Add("Val3");
string response = Blah.SendMessage().Result;
JObject Data = JObject.Parse(response);
foreach (JObject jo in Data["data"])
{
Items.Rows.Add(jo["Val1"], jo["Val2"], jo["Val3"]);
}
}
There is this really nice online utility that helps extracting C# classes from JSON objects. I think the problem here is with your JSON, you're missing a comma ",". You would easily be able to spot the error with some online JSON formatter / validator. Rest the deserialization is pretty straightforward. Try the following:
JObject obtainedObject = JObject.Parse(JsonString);
Following would be the structure of your obtained object:
public class RequiredClass
{
public IList<Datum> data { get; set; }
public Meta meta { get; set; }
}
public class Datum
{
public string Val1 { get; set; }
public string Val2 { get; set; }
public string Val3 { get; set; }
public IList<Link> links { get; set; }
}
public class Link
{
public string rel { get; set; }
public string uri { get; set; }
}
public class Pagination
{
public int total { get; set; }
public int count { get; set; }
public int per_page { get; set; }
public int current_page { get; set; }
public int total_pages { get; set; }
public IList<object> links { get; set; }
}
public class Meta
{
public Pagination pagination { get; set; }
}
Update:
Here's is how you extract your array and convert that to a DataTable:
JObject jObject = JObject.Parse(json);
JToken dataArray = jObject["data"];
DataTable dt = (DataTable) JsonConvert.DeserializeObject(dataArray.ToString(), (typeof(DataTable)));
To avoid the surplus casting, you can try the following using the class structure already mentioned above:
JObject jObject = JObject.Parse(json);
JToken dataArray = jObject["data"];
List<Datum> requiredList = new List<Datum>();
foreach (var item in dataArray)
{
Datum obj = new Datum();
obj.Val1 = (string) item["Val1"] ?? "";
obj.Val2 = (string) item["Val2"] ?? "";
obj.Val3 = (string) item["Val3"] ?? "";
obj.links = new List<Link>();
foreach(var subItem in item["links"])
{
Link lnk = new Link();
lnk.rel = (string) subItem["rel"] ?? "";
lnk.uri = (string) subItem["uri"] ?? "";
obj.links.Add(lnk);
}
requiredList.Add(obj);
}
I need to convert a CSV data (with one line of header, and one line of data) in a JSON object.
The CSV contains nested columns, there is an example:
id,name,category/id,category/name,category/subcategory/id,category/subcategory/name,description
0,Test123,15,Cat123,10,SubCat123,Desc123
And I want a JSON to look like this:
{
"id": 0,
"name": "Test123",
"category": {
"id": 15,
"name": "Cat123",
"subcategory": {
"id": 10,
"name": "SubCat123",
}
},
"description": "Desc123"
}
I've tried CsvHelper and ChoETL libs, without success, because these libs, as far as I know, require that I have a class as model, but I don't have these classes, because the data is totally dynamic.
The site http://www.convertcsv.com/csv-to-json.htm is a good example to make this with success.
Just paste the JSON that I have created above, go to Step 3 and check the option 'Recreate nested objects and arrays', then click 'CSV to JSON' in step 5.
But I need to make this in my application, without using external frameworks.
How can I make this to work?
If you do not have it, add the newtonsoft library(dll) and then add the folowing references
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
Add the following classes
public class Rootobject
{
public int id { get; set; }
public string name { get; set; }
public Category category { get; set; }
public string description { get; set; }
}
public class Category
{
public int id { get; set; }
public string name { get; set; }
public Subcategory subcategory { get; set; }
}
public class Subcategory
{
public int id { get; set; }
public string name { get; set; }
}
then use this code
DataTable CSVData = new DataTable(); // your csv rows
HashSet<Rootobject> MyObjectsList = new HashSet<Rootobject>(); //create hashset to handle your classes
foreach(DataRow row in CSVData.Rows)
{
//change the indices in ItemArray with the right indices
MyObjectsList.Add(new Rootobject() {
id = (int)row.ItemArray[0], name = (string)row.ItemArray[0], category = new Category() {
id = (int)row.ItemArray[0], name = (string)row.ItemArray[0], subcategory = new Subcategory() {
id = (int)row.ItemArray[0], name = (string)row.ItemArray[0]
}
}
});
}
string _ResultObj = JsonConvert.SerializeObject(MyObjectsList); //here get your json string
With the latest ChoETL.JSON 1.0.1.6, you can convert them easily. This will work only if the headers are simple texts, no spaces or special characters allowed.
using (var json = new ChoJSONWriter("nested.json")
.SingleElement()
.Configure(c => c.SupportMultipleContent = true)
.Configure(c => c.DefaultArrayHandling = false)
)
{
using (var csv = new ChoCSVReader("nested.csv").WithFirstLineHeader())
json.Write(csv.Select(i => i.ConvertToNestedObject('/')));
}
Output JSON will look at below
[
{
"id":0,
"name":"Test123",
"category": {
"id": 15,
"name": "Cat123",
"subcategory": {
"id": 10,
"name": "SubCat123"
}
},
"description":"Desc123"
}
]
Sample fiddle: https://dotnetfiddle.net/vttMIB
UPDATE:
Cinchoo ETL now supports native nested object support by just setting 'NestedColumnSeparator' configuration parameter to '/'. Sample below shows how to
using (var json = new ChoJSONWriter("nested.json")
.SingleElement()
.Configure(c => c.SupportMultipleContent = true)
.Configure(c => c.DefaultArrayHandling = false)
)
{
using (var csv = new ChoCSVReader("nested.csv")
.WithFirstLineHeader()
.Configure(c => c.NestedColumnSeparator = '/')
)
json.Write(csv);
}
Sample fiddle: https://dotnetfiddle.net/xFlQso