How can I 'AND' multiple $elemMatch clauses with C# and MongoDB? - c#

I am using the 10Gen sanctioned c# driver for mongoDB for a c# application and for data browsing I am using Mongovue.
Here are two sample document schemas:
{
"_id": {
"$oid": "4ded270ab29e220de8935c7b"
},
"Relationships": [
{
"RelationshipType": "Person",
"Attributes": {
"FirstName": "Travis",
"LastName": "Stafford"
}
},
{
"RelationshipType": "Student",
"Attributes": {
"GradMonth": "",
"GradYear": "",
"Institution": "Test1",
}
},
{
"RelationshipType": "Staff",
"Attributes": {
"Department": "LIS",
"OfficeNumber": "12",
"Institution": "Test2",
}
}
]
},
{
"_id": {
"$oid": "747ecc1dc1a79abf6f37fe8a"
},
"Relationships": [
{
"RelationshipType": "Person",
"Attributes": {
"FirstName": "John",
"LastName": "Doe"
}
},
{
"RelationshipType": "Staff",
"Attributes": {
"Department": "Dining",
"OfficeNumber": "1",
"Institution": "Test2",
}
}
]
}
I need a query that ensures that both $elemMatch criteria are met so that I can match the first document, but not the second. The following query works in Mongovue.
{
'Relationships': { $all: [
{$elemMatch: {'RelationshipType':'Student', 'Attributes.Institution': 'Test1'}},
{$elemMatch: {'RelationshipType':'Staff', 'Attributes.Institution': 'Test2'}}
]}
}
How can I do the same query in my c# code?

There is no way to build above query using c# driver (at least in version 1.0).
But you can build another, more clear query, that will return same result:
{ "Relationships" :
{ "$elemMatch" :
{ "RelationshipType" : "Test",
"Attributes.Institution" : { "$all" : ["Location1", "Location2"] }
}
}
}
And the same query from c#:
Query.ElemMatch("Relationships",
Query.And(
Query.EQ("RelationshipType", "Test"),
Query.All("Attributes.Institution", "Location1", "Location2")));

A simple solution is to string together multiple IMongoQuery(s) and then join them with a Query.And at the end:
List<IMongoQuery> build = new List<IMongoQuery>();
build.Add(Query.ElemMatch("Relationships", Query.EQ("RelationshipType", "Person")));
var searchQuery = String.Format("/.*{0}.*/", "sta");
build.Add(Query.ElemMatch("Relationships", Query.Or(Query.EQ("Attributes.FirstName", new BsonRegularExpression(searchQuery)), Query.EQ("Attributes.LastName", new BsonRegularExpression(searchQuery)))));
var _main = Query.And(build.ToArray());
var DB = MongoDatabase.Create("UrlToMongoDB");
DB.GetCollection<ObjectToQuery>("nameOfCollectionInMongoDB").FindAs<ObjectToQuery>(_main).ToList();
`

I have solved the immediate issue by contruction a set of class that allowed for the generation of the following query:
{ 'Relationships':
{
$all: [
{$elemMatch: {'RelationshipType':'Student', 'Attributes.Institution': 'Test1'}},
{$elemMatch: {'RelationshipType':'Staff', 'Attributes.Institution': 'Test2'}}
]
}
}
Here are the class definitions:
class MongoQueryAll
{
public string Name { get; set; }
public List<MongoQueryElement> QueryElements { get; set; }
public MongoQueryAll(string name)
{
Name = name;
QueryElements = new List<MongoQueryElement>();
}
public override string ToString()
{
string qelems = "";
foreach (var qe in QueryElements)
qelems = qelems + qe + ",";
string query = String.Format(#"{{ ""{0}"" : {{ $all : [ {1} ] }} }}", this.Name, qelems);
return query;
}
}
class MongoQueryElement
{
public List<MongoQueryPredicate> QueryPredicates { get; set; }
public MongoQueryElement()
{
QueryPredicates = new List<MongoQueryPredicate>();
}
public override string ToString()
{
string predicates = "";
foreach (var qp in QueryPredicates)
{
predicates = predicates + qp.ToString() + ",";
}
return String.Format(#"{{ ""$elemMatch"" : {{ {0} }} }}", predicates);
}
}
class MongoQueryPredicate
{
public string Name { get; set; }
public object Value { get; set; }
public MongoQueryPredicate(string name, object value)
{
Name = name;
Value = value;
}
public override string ToString()
{
if (this.Value is int)
return String.Format(#" ""{0}"" : {1} ", this.Name, this.Value);
return String.Format(#" ""{0}"" : ""{1}"" ", this.Name, this.Value);
}
}
Helper Search Class:
public class IdentityAttributeSearch
{
public string Name { get; set; }
public object Datum { get; set; }
public string RelationshipType { get; set; }
}
Example Usage:
public List<IIdentity> FindIdentities(List<IdentityAttributeSearch> searchAttributes)
{
var server = MongoServer.Create("mongodb://localhost/");
var db = server.GetDatabase("IdentityManager");
var collection = db.GetCollection<MongoIdentity>("Identities");
MongoQueryAll qAll = new MongoQueryAll("Relationships");
foreach (var search in searchAttributes)
{
MongoQueryElement qE = new MongoQueryElement();
qE.QueryPredicates.Add(new MongoQueryPredicate("RelationshipType", search.RelationshipType));
qE.QueryPredicates.Add(new MongoQueryPredicate("Attributes." + search.Name, search.Datum));
qAll.QueryElements.Add(qE);
}
BsonDocument doc = MongoDB.Bson.Serialization
.BsonSerializer.Deserialize<BsonDocument>(qAll.ToString());
var identities = collection.Find(new QueryComplete(doc)).ToList();
return identities;
}
I am sure there is a much better way but this worked for now and appears to be flexible enough for my needs. All suggestions are welcome.
This is probably a separate question but for some reason this search can take up to 24 seconds on a document set of 100,000. I have tried adding various indexes but to no avail; any pointers in this regards would be fabulous.

Related

C# return a single list into nested JSON API

I have a simple list which is returned from my DB to my MVC API, the sample query is
select school, class, pupil from area
With that I am adding to a list
foreach (DataRow row in table.Rows)
{
var area = new Area();
area.school = row.ItemArray[0].ToString();
area.class = row.ItemArray[1].ToString();
area.pupil = row.ItemArray[2].ToString();
returnedlist.Add(area);
}
at the moment the API just returns the list
{
"School": "",
"Class": "",
"Pupil": ""
},
{
"School": "",
"Class": "",
"Pupil": ""
},
However, I would ideally like it returned nested like the following
[
{
School: "Name",
Class: [
ClassName: '',
Pupil: [
PupilName: ''},
PupilName: ''},
PupilName: '']
},
{
School: "Name",
Class: [
ClassName: '',
Pupil: [
PupilName: ''},
PupilName: ''},
PupilName: '']
},
I may have butchered that but you get the general idea.
The class for the data again is very simple
public class Area
{
public string School { get; set; }
public string Class { get; set; }
public string Pupil { get; set; }
}
So far I have tried returning a list in a list etc, but without any luck.
Any help greatly appreciated.
To get the expected result, you could create class with this structure :
1 - Classes
public class NewArea
{
public string School { get; set; }
public List<Class> Classes { get; set; }
}
public class Class
{
public string Name { get; set; }
public List<string> Pupils { get; set; }
}
2 - First, group by school and for each grouped item, group by class:
List<NewArea> newAreas = returnedlist
.GroupBy(x => x.School)
.Select(x => new NewArea
{
School = x.Key,
Classes = x.GroupBy(y => y.Class).Select(z => new Class
{
Name = z.Key,
Pupils = z.Select(w => w.Pupil).ToList()
}).ToList()
}).ToList();
3 - Example for test :
List<Area> returnedlist = new List<Area>
{
new Area{School = "s1", Class="c1",Pupil="p1"},
new Area{School = "s1", Class="c1",Pupil="p2"},
new Area{School = "s1", Class="c2",Pupil="p1"},
new Area{School = "s1", Class="c2",Pupil="p2"},
new Area{School = "s2", Class="c1",Pupil="p1"},
};
Result
[
{
"School":"s1",
"Classes":[
{
"Name":"c1",
"Pupils":[
"p1",
"p2"
]
},
{
"Name":"c2",
"Pupils":[
"p1",
"p2"
]
}
]
},
{
"School":"s2",
"Classes":[
{
"Name":"c1",
"Pupils":[
"p1"
]
}
]
}
]
Namespaces :
using System.Linq;
using System.Collections.Generic;

How to select items from json response c#

I am try to create a wild card search feature.
I have a json response it contains the username. i have to search the user like te*, so it will display corresponding usernames.
LIke test1, test2
The below code i am using to get the response
var JSONResponse = await SendGraphRequest("/users/", null, null, HttpMethod.Get);
i have tried below code and trying to filter in graph only
i have try to filter in graph only
var JSON = await SendGraphRequest("/users/", $"$filter=startswith(givenname,'b')", null, HttpMethod.Get);
var graphUserResponse2 = JsonConvert.DeserializeObject<GraphUserResponseMapping>(JSON);
so instead of given name i want to try to filter using user name.
i am using newtonsoft to parse the json but it is difficult to get the username in list then then i will apply the wild card search. but the problem is how to get the username and store in a list?
The below is json response
{
"odata.metadata": "test",
"odata.nextLink":"test",
"value": [
{
"odata.type": "Microsoft.DirectoryServices.User",
"objectType": "User",
"signInNames": [
{
"type": "emailAddress",
"value": "test1#gmail"
},
{
"type": "username",
"value": "Test1"
}
],
"personId": "1"
},
{
"odata.type": "Microsoft.DirectoryServices.User",
"objectType": "User",
"signInNames": [
{
"type": "emailAddress",
"value": "test2#gmail.com"
},
{
"type": "username",
"value": "Test2"
}
],
"personId": "2"
}
]
}
TIA
Roger!
You can use a Class ex:
YourClassName.cs
code inside this class
public class SignInName
{
public string Type { get; set; }
public string Value { get; set; }
}
public class Value
{
[JsonProperty(PropertyName = "odata.type")]
public string OdataType { get; set; }
public string ObjectType { get; set; }
public List<SignInName> SignInNames { get; set; }
public string PersonId { get; set; }
}
public class YourClassName
{
[JsonProperty(PropertyName = "odata.metadata")]
public string OdataMetadata { get; set; }
[JsonProperty(PropertyName = "odata.nextLink")]
public string OdataNextLink { get; set; }
public List<Value> Value { get; set; }
}
So you can search for the usernames and put it into a list.
Ex:
List<string> userNameList = new List<string>();
var json = "{ \"odata.metadata\": \"test\", \"odata.nextLink\":\"test\", \"value\": [ { \"odata.type\": \"Microsoft.DirectoryServices.User\", \"objectType\": \"User\", \"signInNames\": [ { \"type\": \"emailAddress\", \"value\": \"test1#gmail\" }, { \"type\": \"username\", \"value\": \"Test1\" } ], \"personId\": \"1\" }, { \"odata.type\": \"Microsoft.DirectoryServices.User\", \"objectType\": \"User\", \"signInNames\": [ { \"type\": \"emailAddress\", \"value\": \"test2#gmail.com\" }, { \"type\": \"username\", \"value\": \"Test2\" } ], \"personId\": \"2\" } ] }";
var yourClassName = JsonConvert.DeserializeObject<YourClassName>(json);
foreach (var value in yourClassName.Value)
{
userNameList.AddRange(value.SignInNames.Where(x => x.Type == "username").Select(x => x.Value).ToList());
}

Getting Data from a JObject

I have stored JSON data in a string and by using the JObject, I am trying to get values from JSON data. I am just not able to figure out that what is the underlying issue with my code because I am not able to get data from the JSON object. A snippet of my code is attached below. If some can help me out to figure out the issue it will be immensely appreciated.
String text;
try {
var response = (HttpWebResponse)request.GetResponse();
using (var sr = new StreamReader(response.GetResponseStream()))
{
text = sr.ReadToEnd();
JObject jObject = JObject.Parse(text);
string haha = (string)jObject["value/segments/requests/count/sum"];
ViewBag.gotstring = haha;
}
System.Diagnostics.Debug.WriteLine(text);
} catch(Exception e) {
System.Diagnostics.Debug.WriteLine(url);
System.Diagnostics.Debug.WriteLine(e.ToString()); }
return View();
Here is the JSON:
{
"value": {
"start": "2018-08-12T04:44:38.941Z",
"end": "2018-08-12T16:44:38.941Z",
"interval": "PT30M",
"segments": [
{
"start": "2018-08-12T14:00:00Z",
"end": "2018-08-12T14:30:00Z",
"segments": [
{
"requests/count": {
"sum": 2
},
"request/name": "GET Home/Index"
},
{
"requests/count": {
"sum": 1
},
"request/name": "GET Home/About"
},
{
"requests/count": {
"sum": 1
},
"request/name": "GET Home/Contact"
}
]
},
{
"start": "2018-08-12T14:30:00Z",
"end": "2018-08-12T15:00:00Z",
"segments": [
{
"requests/count": {
"sum": 2
},
"request/name": "GET Account/Register"
},
{
"requests/count": {
"sum": 1
},
"request/name": "GET Account/Login"
}
]
},
{
"start": "2018-08-12T15:30:00Z",
"end": "2018-08-12T16:00:00Z",
"segments": [
{
"requests/count": {
"sum": 8
},
"request/name": "GET Home/Index"
},
{
"requests/count": {
"sum": 8
},
"request/name": "GET Home/About"
}
]
}
]
}
}
jObject does not work this way. It returns dictionary that you can query by key, but keys are single level. I.e. you'll be able to get some data like this:
var haha = jObject["value"]["segments"];
But beyond that it gets very complex. You'll be much better off defining a C# class that represents your JSON and serialise into that. A simple `Edit=>Paste Special => JSON as Class" in Visual Studio gives this:
public class Rootobject
{
public Value value { get; set; }
}
public class Value
{
public DateTime start { get; set; }
public DateTime end { get; set; }
public string interval { get; set; }
public Segment[] segments { get; set; }
}
public class Segment
{
public DateTime start { get; set; }
public DateTime end { get; set; }
public Segment1[] segments { get; set; }
}
public class Segment1
{
[JsonProperty("requests/count")]
public RequestsCount requestscount { get; set; }
[JsonProperty("request/name")]
public string requestname { get; set; }
}
public class RequestsCount
{
public int sum { get; set; }
}
and then deserialise like this:
var serialised = JsonConvert.DeserializeObject<Rootobject>(json);
var haha = serialised.value.segments.FirstOrDefault().segments.FirstOrDefault().requestscount.sum;
And here is a working sample: https://dotnetfiddle.net/CZgMNE
Can you try:
EDIT: seems like segments is an array, this will get you the sum for first segment only
string haha = (string)jObject["value"]["segments"][0]["segments"]["requests/count"]["sum"];

How to Deserialize JSON array(or list) in C#

public static string DeserializeNames()
{
// Json I am passing for the deserialization.
JsonStream= "{
"head": {
"Rows": [
"test 1",
[
[
{
"#Key": "Domain",
"#value": "LocalHost"
},
{
"#Key": "Cookie name(s)",
"#value": "Google"
},
{
"#Key": "Purpose",
"#value": "Test"
},
{
"#Key": "lifetime",
"#value": "test"
}
]
]
]
}
}"
//deserialize JSON from file
JavaScriptSerializer serializer = new JavaScriptSerializer();
var cookieList = serializer.Deserialize<List<Cookie>>(JsonStream).ToList();
}
//Class descriptions
//I have created below classes for the deserialization. Records are not deserialized i am getting zero record count.
public class Categorization
{
public string categorizationName { get; set; }
public List<object> KeyValue{ get; set; }
}
public class Head
{
public IList<Categorization> Rows { get; set; }
}
public class Cookie
{
public Head head { get; set; }
}
Also created below set of the classes and tried the deserialization, Still no luck
public class Head
{
public List<object> Rows { get; set; }
}
public class Cookie
{
public Head head { get; set; }
}
I am getting count as 0 i am not able to fetch any record.
Please help !!
I have modified the Json as below and stored in in the file "test.json" :
{
"head": {
"TestRows": [
[
{
"Key": "Domain",
"value": "Google"
},
{
"Key": "Cookie",
"value": "Test"
},
{
"Key": "Use for",
"value": "Test."
},
{
"Key": "Expire Time",
"value": "1 hour"
}
]
]
}
}
And created below set of classes :
public class Head
{
public IList<List<Dictionary<string, object>>> TestRows{ get; set; }
}
public class Cookie
{
public Head Head { get; set; }
}
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
var path= Path.Combine(baseDirectory, "test.json");
//deserialize JSON from file
string JsonStream = System.IO.File.ReadAllText(path, Encoding.Default);
var DeserializedCookieList = JsonConvert.DeserializeObject<Cookie>(JsonStream);
Deserialization is working properly.

Convert Json object to List

I have this Json string :
"UserProperties": [
{
"Id": "3", "values": [
{ "prop": "1" }
]
},
{
"Id": "4", "values": [
{ "prop": "1" },
{ "prop": "2" },
{ "prop": "3" }
]
}
]
I Want to convert this into some sort of c# string and list value like this:
public list<int> Id { get; set; }
public list<object> values { get; set; }
public int prop { get; set; }
So that i can manipulate my values :
foreach( int i in Id)
{
foreach( object val in values)
{
var str = i + '-' + val.prop;
}
}
So far what i have done is create a class that will contain those Json string. I get this code from an quite similar approach.
Create a wrapper class
class Wrapper {
[JsonProperty("UserPropertiees")]
public ValueSet valueSet { get; set; }
}
class ValueSet{
[JsonProperty("values")]
public List<string> values {get;set;}
public PropertySet propertySet { get; set; }
}
class PropertySet{
[JsonProperty("property")]
public List<string> property {get;set;}
}
I would use Newtonsoft's Json.NET and deserialize your JSON array into your List.
http://www.newtonsoft.com/json/help/html/SerializingCollections.htm
As per Rob Stewart's advice, NewtonSoft.JSON is the best way to go IMO. Here's something you can put in a console app to have a play with:
string json = #"{""UserProperties"": [
{
""Id"": ""3"", ""values"": [
{ ""prop1"": ""1"" }
]
},
{
""Id"": ""4"", ""values"": [
{ ""prop1"": ""1"" },
{ ""prop2"": ""2"" },
{ ""prop3"": ""3"" }
]
}
]}";
dynamic obj = JObject.Parse(json);
foreach (var o in obj.UserProperties)
{
Console.WriteLine(o.Id);
}
Console.ReadLine();
EDIT
As per your comments, here is a more complete example. Try this:
dynamic obj = JObject.Parse(json);
foreach (var o in obj.UserProperties)
{
var sb = new StringBuilder();
sb.Append(o.Id);
sb.Append(":");
bool hasProps = false;
foreach (var value in o.values)
{
if (value.prop1 != null)
{
sb.Append(value.prop1);
sb.Append(',');
hasProps = true;
}
if (value.prop2 != null)
{
sb.Append(value.prop2);
sb.Append(',');
hasProps = true;
}
if (value.prop3 != null)
{
sb.Append(value.prop3);
sb.Append(',');
hasProps = true;
}
}
if (hasProps)
{
sb.Remove(sb.Length - 1, 1); // Remove trailing comma
}
Console.WriteLine(sb.ToString());
}
Console.ReadLine();

Categories

Resources