So Suppose I have two Collections
User {
string Id;
string name;
}
Category {
string Id;
string userId;
string name;
}
// For Look Up
UserJoined : User {
public ICOllection<Category> Categories {get;set;}
}
I am trying to count using this kind of Query (SQL)
Expression<Func<User,bool>> query = p => p.User.Category.Name == "Apple";
Now the above works great in SQL using EF Core.
But I want to do something similar in Mongo DB Driver C#.
The Current situation fetches all the documents and then it Takes count, which is very very bad performance wise in the long run. I know there might be something.
I was looking at AddFields operator but I couldn't find a way to use it in my case.
This should get the count of users which contain a category with the name "Apple":
MongoClient mongoClient = new MongoClient("mongodb://localhost:27017");
IMongoDatabase db = mongoClient.GetDatabase("your_db_name");
IMongoCollection<BsonDocument> usersCollection = db.GetCollection<BsonDocument>("users");
FilterDefinition<BsonDocument> matchFilter = Builders<BsonDocument>.Filter.ElemMatch<BsonDocument>("categories", Builders<BsonDocument>.Filter.Eq(c => c["name"], "Apple"));
AggregateCountResult result = usersCollection.Aggregate()
.Match(matchFilter)
.Count()
.Single();
long count = result.Count;
Which is a C# representation of the following aggregation:
db.users.aggregate(
[{
$match: {
categories: {
$elemMatch: {
name: "Apple"
}
}
}
}, {
$count: "count"
}]
)
Considering you have users collection with the following data:
[
{
"_id": 1,
"name": "User 1",
"categories": [
{
"_id": 1,
"name": "Orange"
},
{
"_id": 2,
"name": "Apple"
},
{
"_id": 3,
"name": "Peach"
}
]
},
{
"_id": 2,
"name": "User 2",
"categories": [
{
"_id": 1,
"name": "Orange"
},
{
"_id": 3,
"name": "Peach"
}
]
},
{
"_id": 3,
"name": "User 3",
"categories": [
{
"_id": 4,
"name": "Banana"
}
]
},
{
"_id": 4,
"name": "User 4",
"categories": [
{
"_id": 4,
"name": "Banana"
},
{
"_id": 2,
"name": "Apple"
}
]
},
{
"_id": 5,
"name": "User 5",
"categories": [
{
"_id": 2,
"name": "Apple"
}
]
}
]
Value of count variable will be 3.
if the goal is to get a count of users who has "Apple" as a category, you can simply do it like so:
var appleUserCount = await categoryCollection.CountDocumentsAsync(c=>c.Name == "Apple");
however, if there are duplicate categories (same userId and categoryName) then getting an accurate count gets a bit complicated and you'll have to do some grouping for getting a distinct user count.
test program:
using MongoDB.Entities;
using System.Threading.Tasks;
namespace TestApplication
{
public class User : Entity
{
public string Name { get; set; }
}
public class Category : Entity
{
public string UserID { get; set; }
public string Name { get; set; }
}
public static class Program
{
private static async Task Main()
{
await DB.InitAsync("test");
await DB.Index<Category>()
.Key(c => c.Name, KeyType.Ascending)
.CreateAsync();
var user1 = new User { Name = "Fanboy" };
var user2 = new User { Name = "Poolboy" };
await new[] { user1, user2 }.SaveAsync();
await new[] {
new Category { Name = "Apple", UserID = user1.ID},
new Category { Name = "Apple", UserID = user2.ID},
new Category { Name = "Google", UserID = user1.ID}
}.SaveAsync();
var count = await DB.CountAsync<Category>(c => c.Name == "Apple");
}
}
}
Related
I am struggling now since hours to get one query working. My shema looks like the following:
public class ActionResponse
{
public string Id { get; set; }
public List<ActionResponseItem> ResponseItems { get; set; }
}
public class ActionResponseItem
{
public string Id { get; set; }
public DateTime Created { get; set; }
public List<string> Options { get; set; }
}
Let's assume I've following collection:
[
{
"_id": ObjectId("62bd901c4af4e9d2e1287984"),
"ResponseItems": [
{
"_id": ObjectId("62bd9068b4598dd0bb07b536"),
"Created": ISODate("2022-05-30T08:45:31.662Z"),
"Options": [
"Some string 1",
"Some string 2"
]
},
{
"_id": ObjectId("62bd906e35087e146b6b28f6"),
"Created": ISODate("2022-05-30T08:46:56.192Z"),
"Options": [
"Some string 3",
"Some string 4"
]
}
]
},
{
"_id": ObjectId("5e29560a2c4c55d421a4e1b4"),
"ResponseItems": [
{
"_id": ObjectId("62bd62ab886b3fde8368ec39"),
"Created": ISODate("2022-06-30T08:45:31.662Z"),
"Options": [
"Some string 1",
"Some string 2"
]
},
{
"_id": ObjectId("62bd6300886b3fde8368ec3f"),
"Created": ISODate("2022-06-30T08:46:56.192Z"),
"Options": [
"Some string 3",
"Some string 4"
]
},
{
"_id": ObjectId("62bd6349886b3fde8368ec46"),
"Created": ISODate("2022-06-30T08:48:09.226Z"),
"Options": null
},
{
"_id": ObjectId("62bd64a88f5b6b24b6de199e"),
"Created": ISODate("2022-06-30T08:54:00.450Z"),
"Options": null
},
{
"_id": ObjectId("62bd64aa8f5b6b24b6de19a7"),
"Created": ISODate("2022-06-30T08:54:02.767Z"),
"Options": null
},
{
"_id": ObjectId("62bd64af8f5b6b24b6de19b1"),
"Created": ISODate("2022-06-30T08:54:07.896Z"),
"Options": null
},
{
"_id": ObjectId("62bd64b38f5b6b24b6de19bc"),
"Created": ISODate("2022-06-30T08:54:11.837Z"),
"Options": null
},
{
"_id": ObjectId("62bd64b78f5b6b24b6de19c8"),
"Created": ISODate("2022-06-30T08:54:15.588Z"),
"Options": [
"Some string 5",
"Some string 6"
]
},
{
"_id": ObjectId("62bd64bd8f5b6b24b6de19d5"),
"Created": ISODate("2022-06-30T08:54:21.494Z"),
"Options": [
"Some string 7"
]
},
{
"_id": ObjectId("62bd654d8f5b6b24b6de331d"),
"Created": ISODate("2022-06-30T08:56:45.487Z"),
"Options": null
}
]
}
]
I want to get a List where several conditions matches, e.g.:
ActionResponse.Id = '5e29560a2c4c55d421a4e1b4' & ActionResponseItem.Created >= ISODate("2022-06-30T08:54:17Z") & ActionResponseItem.Created <= ISODate("2022-06-30T10:09:18.403Z")
and return only the nested elements like:
[
{
"_id": ObjectId("62bd64b78f5b6b24b6de19c8"),
"Created": ISODate("2022-06-30T08:54:15.588Z"),
"Options": [
"Some string 5",
"Some string 6"
]
},
{
"_id": ObjectId("62bd64bd8f5b6b24b6de19d5"),
"Created": ISODate("2022-06-30T08:54:21.494Z"),
"Options": [
"Some string 7"
]
},
{
"_id": ObjectId("62bd654d8f5b6b24b6de331d"),
"Created": ISODate("2022-06-30T08:56:45.487Z"),
"Options": null
}
]
I've tried several queries (with/without aggregation, projection) and so on...
With this query only the first matching element is returned:
Mongo
db.collection.find({
"_id": ObjectId("5e29560a2c4c55d421a4e1b4"),
"ResponseItems": {
"$elemMatch": {
"Created": {
"$gte": ISODate("2022-06-30T08:54:17Z"),
"$lte": ISODate("2022-06-30T10:13:21.754Z")
}
}
}
},
{
"ResponseItems": {
"$elemMatch": {
"Created": {
"$gte": ISODate("2022-06-30T08:54:17Z"),
"$lte": ISODate("2022-06-30T10:13:21.754Z")
}
}
}
})
C#
var filter = Builders<ActionResponse>.Filter.Eq(a => a.Id, id);
var nestedFilter = Builders<ActionResponseItem>.Filter.Gte(x => x.Created, from) &
Builders<ActionResponseItem>.Filter.Lte(x => x.Created, to);
var elements = await _collection
.Find(filter & Builders<ActionResponse>.Filter.ElemMatch(b => b.ResponseItems, nestedFilter))
.Project(Builders<ActionResponse>.Projection
.ElemMatch(c => c.ResponseItems, nestedFilter))
.ToListAsync();
With this the whole document is returned:
Mongo
db.collection.aggregate([
{
"$match": {
"_id": ObjectId("5e29560a2c4c55d421a4e1b4"),
"ResponseItems": {
"$elemMatch": {
"Created": {
"$gte": ISODate("2022-06-30T08:54:17Z"),
"$lte": ISODate("2022-06-30T10:09:18.403Z")
}
}
}
}
}
])
C#
var filter = Builders<ActionResponse>.Filter.Eq(a => a.Id, id);
var nestedFilter = Builders<ActionResponseItem>.Filter.Gte(x => x.Created, from) &
Builders<ActionResponseItem>.Filter.Lte(x => x.Created, to);
var elements = await _collection.Aggregate()
.Match(filter & Builders<ActionResponse>.Filter.ElemMatch(b => b.ResponseItems, nestedFilter))
.ToListAsync();
Maybe there is only a small thing to change and it will work. I hope somebody can help me out on this problem.
MongoDb Playground
THX
something like this should work, you need to do a match, unwind, match and then replace the root
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
var client = new MongoClient();
var itemFilter =
Builders<UnwindResponseItemsActionResponse>.Filter.Gte(x => x.ResponseItems.Created,
new DateTime(2022, 06, 30, 08, 54, 17, DateTimeKind.Utc))
& Builders<UnwindResponseItemsActionResponse>.Filter.Lte(x => x.ResponseItems.Created,
new DateTime(2022, 06, 30, 10, 09, 18, 403, DateTimeKind.Utc));
var actionResponseItems = await client.GetDatabase("test")
.GetCollection<ActionResponse>("actions")
.Aggregate()
.Match(Builders<ActionResponse>.Filter.Eq(x => x.Id, "5e29560a2c4c55d421a4e1b4"))
.Unwind<ActionResponse, UnwindResponseItemsActionResponse>(x => x.ResponseItems)
.Match(itemFilter)
.ReplaceRoot(x => x.ResponseItems)
.ToListAsync();
foreach (var item in actionResponseItems)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Created);
Console.WriteLine();
}
/*
*
62bd64bd8f5b6b24b6de19d5
30/06/2022 08:54:21
62bd654d8f5b6b24b6de331d
30/06/2022 08:56:45
*/
public class UnwindResponseItemsActionResponse
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public ActionResponseItem ResponseItems { get; set; }
}
public class ActionResponse
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public List<ActionResponseItem> ResponseItems { get; set; }
}
public class ActionResponseItem
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public DateTime Created { get; set; }
public List<string> Options { get; set; }
}```
I want to return a singular json object with all distinct fields inside that object. The below code generates this json which returns three entries in value where are the same except for the last two fields.
{
"value": [
{
"name": "russ",
"id": "12345",
"grade": 5,
"TestID": "12332",
"testResult": "Pass"
},
{
"name": "russ",
"id": "12345",
"grade": 5,
"TestID": "15474",
"testResult": "Pass"
},
{
"name": "russ",
"id": "12345",
"gradeLevel": 5,
"TestID": "75783",
"testResult": "Fail"
}
]
}
Here is my code that performs a web request to get this data
var result = Helper.DoRequest(requestUrl, null, httpMethod, "");
var jObj = JObject.Parse(result.Last().Value.ToString());
JArray valueList = JArray.Parse(jObj["value"].ToString());
I would like to consolidate that into:
{
"value": [
{
"name": "russ",
"id": "12345",
"grade": 5,
"TestID": "12332",
"testResult": "Pass"
"TestID": "15474",
"testResult": "Pass"
"TestID": "75783",
"testResult": "Fail"
}]
}
This code may be a start. It deserializes the json into classes, and groups the results by Name, ID and Grade (I'm assuming the 'gradelevel' key in the third result was a typo) and then outputs that as Json with the test results as an array (your desired output is not valid Json as it contains duplicate key names):
void Main()
{
var json = json_from_api;
var results = JsonConvert.DeserializeObject<RootObject>(json);
var groupedResults = results.value.GroupBy(r => new { r.id, r.name, r.grade });
var finalResults = groupedResults.Select(g => new
{
g.Key.id,
g.Key.name,
g.Key.grade,
test_results = g.ToList().Select(v => new
{
v.TestID,
v.testResult
}
)});
var output = new {
value = finalResults
};
Console.WriteLine(JsonConvert.SerializeObject(output, Newtonsoft.Json.Formatting.Indented));
}
public class Value
{
public string name { get; set; }
public string id { get; set; }
public int grade { get; set; }
public string TestID { get; set; }
public string testResult { get; set; }
}
public class RootObject
{
public List<Value> value { get; set; }
}
Output:
{
"value": [
{
"id": "12345",
"name": "russ",
"grade": 5,
"test_results": [
{
"TestID": "12332",
"testResult": "Pass"
},
{
"TestID": "15474",
"testResult": "Pass"
},
{
"TestID": "75783",
"testResult": "Fail"
}
]
}
]
}
I'm wondering if someone can elucidate a method to sort a list of objects based on a child object's attribute.
I'm working with the following model:
public class Content
{
public string Id { get; set; }
public List<ContentAttribute> Attributes { get; set; }
}
public class ContentAttribute
{
public string Value { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
Some sample data:
[
{
"Id": "123",
"Attributes": [
{
"Value": "abc",
"Id": "1a",
"Name": "name1"
},
{
"Value": "ghi",
"Id": "2b",
"Name": "name2"
}
]
},
{
"Id": "456",
"Attributes": [
{
"Value": "abc",
"Id": "1a",
"Name": "name2"
},
{
"Value": "def",
"Id": "2b",
"Name": "name3"
}
]
},
{
"Id": "789",
"Attributes": [
{
"Value": "abc",
"Id": "1a",
"Name": "name1"
},
{
"Value": "def",
"Id": "2b",
"Name": "name2"
}
]
}
]
How can I sort the Content objects by the Value of a specific attribute Name? For example, I would like to sort the above data by the Value of 'name2',
meaning the result would be
[
{"Id" : "456"},
{"Id" : "789"},
{"Id" : "123"}
]
Any help is greatly appreciated. (Using c#).
If Attributes always has an element with name name2 and you want an exception if it doesn't then:
var sorted = contents.OrderBy(c => c.Attributes.First(a => a.Name == "name2").Value).ToList();
Or if name2 could be missing and it's not deal breaker then use FirstOrDefault
var sorted = contents.OrderBy(c => c.Attributes.FirstOrDefault(a => a.Name == "name2")?.Value).ToList();
My approximate data table structure which i will get from my sqlserver stored proc call. I am using .netframework 3.5 and i want to convert this datatable to json output using ado.net. i have stuck at GetCountryList(FK,TypeName). Kindly help me to acheive the below json output. Thanks for your help in advance.
Type ID Name FK
Continent 1 America 0
Continent 2 Asia 0
Continent 3 Africa 0
Country 11 USA 1
Country 12 China 2
Country 13 India 2
Country 14 Kenya 3
DataEntity
public class UserData
{
public string Type { get; set; }
public string ID { get; set; }
public string Name { get; set; }
public string FK {get; set;}
}
Service Method
public static UserData GetUserData() {
JavaScriptSerializer jSerializer = new JavaScriptSerializer ();
DataTable dtUserData = DataAccess.getUserDataTable();
if(dtUserData !=null && dtUserData.Rows.Count>0)
{
List<DataRow> list = dtMasterData.AsEnumerable().ToList();
List<UserData> lstContinent = new List<UserData>();
List<UserData> lstCountry = new List<UserData>();
foreach(DataRow dr in list)
{
var objUserData = new UserData();
objUserData.ID = dr["ID"].ToString();
objUserData.Type = dr["Type"].ToString();
objUserData.Name = dr["Name"].ToString();
objUserData.FK = dr["FK"].ToString();
if(objUserData.Type.ToString().ToLower=="continent")
{
lstContinent.Add(objUserData);
}
if(objUserData.Type.ToString().ToLower=="country")
{
if(dr["FK"] !=null)
{
var ForgnKey = dr["FK"].ToString();
var TypeName = dr["Type"].ToString();
var CountriesList = GetCountryList(FK,TypeName) //how do i call a generic method to filter out the country list as per passing Continent FK?
lstCountry.AddRange(CountriesList);
}
lstCountry.Add(objUserData);
}
private static List<T> GetCountryList (lstCountry,ForgnKey,TypeName) //Not sure with the syntax
{
var CountriesList = lstCountry.Where(p=>p.FK==ForgnKey).ToList();
}
}
return jSerializer.Serialize(objUserData);
}
Expected JSON Output :
"data": {
"Contnient":
[
{ "Id": "1", "Type": "Contient", "Name" :"America","FK":"1" },
{ "Id": "2", "Type": "Contient", "Name" :"Asia", "FK":"2" },
{ "Id": "3", "Type": "Contient", "Name" :"Africa", "FK":"2" },
{ "Id": "4", "Type": "Contient", "Name" :"Asia", "FK":"2" }
],
"America":
{
"Country":
[
{ "Id": "11", "Type": "Country","Name":"India","FK":"1" }
]
},
"Asia:
{
"Country":
[ { "Id": "12", "Type": "Country","Name":"China","FK":"2" },
{ "Id": "13", "Type": "Country","Name":"India","FK":"2" }
]
}
"Africa":
{
"Country":
[ { "Id": "14", "Type": "Country","Name":"Kenya","FK":"3" }
]
}
Shouldn't the generic function be defined as:
private static List<T> GetCountryList<T>(lstCountry,ForgnKey,TypeName)
{
return lstCountry.Where(p=>p.FK==ForgnKey).ToList();
}
Then called as:
var CountriesList = GetCountryList<UserData>(lstCountry,FK,TypeName);
C# | .NET 4.5 | Entity Framework 5
I have data coming back from a SQL Query in the form of ID,ParentID,Name. I'd like to take that data and parse it into a Hierarchical JSON string. So far it seems to be much more of a daunting task than it should be. Since I'm using Entity the data comes back nicely to me as an IEnumerable. Now I believe I just need some form of recursion, but I'm not quite sure where to start. Any help is appreciated.
Data Returns as
id parentId name
1 1 TopLoc
2 1 Loc1
3 1 Loc2
4 2 Loc1A
Code is
public static string GetJsonLocationHierarchy(long locationID)
{
using (EntitiesSettings context = new EntitiesSettings())
{
// IEnumerable of ID,ParentID,Name
context.GetLocationHierarchy(locationID);
}
}
The end result I'd hope would be something like this:
{
"id": "1",
"parentId": "1",
"name": "TopLoc",
"children": [
{
"id": "2",
"parentId": "1",
"name": "Loc1",
"children": [
{
"id": "4",
"parentId": "2",
"name": "Loc1A",
"children": [
{}
]
}
]
},
{
"id": "3",
"parentId": "1",
"name": "Loc2",
"children": [
{}
]
}
]
}
One way to turn a flat table into a hierarchy is to put all your nodes into a dictionary. Then iterate over the dictionary, and for each node, look up its parent and add it to the parent's children. From there, you just need to find the root and serialize it.
Here is an example program to demonstrate the approach:
class Program
{
static void Main(string[] args)
{
IEnumerable<Location> locations = new List<Location>
{
new Location { Id = 1, ParentId = 1, Name = "TopLoc" },
new Location { Id = 2, ParentId = 1, Name = "Loc1" },
new Location { Id = 3, ParentId = 1, Name = "Loc2" },
new Location { Id = 4, ParentId = 2, Name = "Loc1A" },
};
Dictionary<int, Location> dict = locations.ToDictionary(loc => loc.Id);
foreach (Location loc in dict.Values)
{
if (loc.ParentId != loc.Id)
{
Location parent = dict[loc.ParentId];
parent.Children.Add(loc);
}
}
Location root = dict.Values.First(loc => loc.ParentId == loc.Id);
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Formatting.Indented
};
string json = JsonConvert.SerializeObject(root, settings);
Console.WriteLine(json);
}
}
class Location
{
public Location()
{
Children = new List<Location>();
}
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
public List<Location> Children { get; set; }
}
Here is the output:
{
"id": 1,
"parentId": 1,
"name": "TopLoc",
"children": [
{
"id": 2,
"parentId": 1,
"name": "Loc1",
"children": [
{
"id": 4,
"parentId": 2,
"name": "Loc1A",
"children": []
}
]
},
{
"id": 3,
"parentId": 1,
"name": "Loc2",
"children": []
}
]
}