Dot notation access to MongoDB Query results (BsonDocuments) in C# - c#

How can I access the MongoCursor attributes in C#.
I have the following line of code:
MongoCursor results = collection.Find(searchQuery).SetLimit(10).SetFields(
Fields.Include("name1","name", "_id"));
MongoDB returns an array, each one with two attributes: name and name1. In the results View in the debugger,, I can see an array, each item in the array contains a MongoDB.Bson.BsonDocument.
I want to have a dot notation access to the attributes of each BsonDocument in the Array. How can I achieve this.?

Came across this issue when I had to work with raw BsonDocuments. This solution does allow you to use dot notation.
Not fully tested or anything, but may be useful.
void Main()
{
var example = new BsonDocument{
{ "customer", new BsonDocument { { "email" , "homerjay#simpsons.com" } }}
};
var email = example.GetPath("customer.email").AsString;
Console.WriteLine(email);
}
public static class MongoDbHelper
{
public static BsonValue GetPath(this BsonValue bson, string path)
{
if (bson.BsonType != BsonType.Document)
{
throw new Exception("Not a doc");
}
var doc = bson.AsBsonDocument;
var tokens = path.Split(".".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (tokens.Length == 0 )
{
return doc;
}
if (!doc.Contains(tokens[0]))
{
return BsonNull.Value;
}
if (tokens.Length > 1)
{
return GetPath(doc[tokens[0]], tokens[1]);
}
return doc[tokens[0]];
}
}

To get values out of a BsonDocument you can use the GetValue/TryGetValue methods or the indexer:
foreach (var document in results)
{
var name1 = document.GetValue("name1");
var name = document["name"];
}

Related

Is there a built-in method to convert .NET Configuration to JSON?

It's easy to convert JSON into configuration, e.g. with
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
var configuration = new ConfigurationBuilder().AddJsonStream(stream).Build();
which gives you an IConfigurationRoot.
Is there a method (preferably in one of the Microsoft.Extensions.Configuration packages) that does the reverse?
Context: I'm downloading a bunch of Azure App Configuration settings and want to export them as JSON. Similar functionality is available in the Azure Portal but I want to resolve key vault references as well.
I can probably do something like this:
// Convert to JSON
var jRoot = new JObject();
foreach (var setting in settings) {
Add(jRoot, setting.Key, setting.Value);
}
with the Add method defined as
private void Add(JObject jObject, string key, string value) {
var index = key.IndexOf(':');
if (index == -1) {
jObject[key] = value;
return;
}
var prefix = key[..index];
if (!jObject.ContainsKey(prefix)) {
jObject[prefix] = new JObject();
}
Add((JObject)jObject[prefix], key[(index + 1)..], value);
}
which I'd probably need to extend to support arrays, but I was hoping I'd not have to reinvent the wheel.
For now, I've expanded the Add method to support arrays:
private void Add(JToken jToken, string key, string value) {
var components = key.Split(":", 3);
if (components.Length == 1) {
// Leaf node
if (jToken is JArray jArray_) {
jArray_.Add(value);
} else {
jToken[components[0]] = value;
}
return;
}
// Next level
JToken nextToken;
var nextTokenIsAnArray = int.TryParse(components[1], out _);
if (jToken is JArray jArray) {
var index = int.Parse(components[0]);
if (jArray.Count == index) {
nextToken = nextTokenIsAnArray ? new JArray() : (JToken)new JObject();
jArray.Add(nextToken);
} else {
nextToken = jArray[index];
}
} else {
nextToken = jToken[components[0]];
if (nextToken == null) {
nextToken = jToken[components[0]] = nextTokenIsAnArray ? new JArray() : (JToken)new JObject();
}
}
Add(nextToken, key[(components[0].Length + 1)..], value);
}
which works for me, assuming arrays appear in the right order, e.g.
key "Foo:0", value "Bar"
key "Foo:1", value "Baz"
which gets serialized as
{ "Foo": ["Bar", "Baz"] }

Replace ID from json entity with random number without creating entity class

I have testdata.json with object like this:
"Entity": {
"ID": "1",
"name": "some name",
"City": "some city",
"address": "some address"
}
Here are my 2 methods for withdrawing and deserializing this entity:
public static string ReadSettings(string name)
{
var parts = name.Split('.', StringSplitOptions.RemoveEmptyEntries);
JObject jObj = GetObject();
foreach (var part in parts)
{
var token = jObj[part];
if (token == null)
{
return null;
}
else
{
return token.ToString();
}
}
return null;
}
private static JObject GetObject()
{
if (_jObject != null)
{
return _jObject;
}
var filename = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
"testdata.json");
var json = File.ReadAllText(filename);
_jObject = JObject.Parse(json);
return _jObject;
}
My question is next: is there any way I can replace Entity.ID with random number (I need this in tests, where I create new Entity - every time test runs - there should be new Entity with new unique ID)?
p.s. I'm just learning C# so don't judge me hardly if it's simple question =)
I suppose the given testdata.json content is not the real thing since running JObject.Parse() on it gives an exception. However, you can navigate to a property with the string key indexer of the JObject class and change it as:
// Generate a new random value.
var random = new Random();
var randomValue = random.Next(minValue, maxValue);
// Navigate to the property and assign the random value as a string.
_jObject["Entity"]["ID"] = randomValue.ToString();
The property navigation of course needs to match the structure of your Json.

MongoDB result set for Aggregate()

I started out with Mongo client doing some nifty queries and aggretations.. but now that I want to use it in .NET/C#, I see that I can't simply run the query as text field..
Furthermore, after resorting to building an Aggregation Pipeline, and running the collection.Aggregate() function, I'm getting a result set, but I have no idea how to traverse it..
Can anyone help guide me here?
Here's my code:
var coll = db.GetCollection("animals");
var match = new BsonDocument {
{ "$match", new BsonDocument {{"category","cats"}} }
};
var group = new BsonDocument{
{
"$group", new BsonDocument{
{"_id", "$species"},
{"AvgWeight", new BsonDocument{{"$avg", "$weight"}}} }
}
};
var sort = new BsonDocument{{"$sort", new BsonDocument{{"AvgWeight", -1}}}};
var pipeline = new[] { match, group, sort };
var args = new AggregateArgs { Pipeline = pipeline };
var res = coll.Aggregate(args);
foreach (var obj in res)
{
// WHAT TO DO HERE??
}
Also, I should say that I'm a little rusty with C# / ASP.NET / MVC so any room for simplification would be much appreciated.
Your result is IEnumerable of BsonDocument, you can Serialize them to C# objects using the BSonSerializer. And this code snippet just writes them to your console, but you can see that you have typed objects
List<Average> returnValue = new List<Average>();
returnValue.AddRange(documents.Select(x=> BsonSerializer.Deserialize<Average>(x)));
foreach (var obj in returnValue)
{
Console.WriteLine("Species {0}, avg weight: {1}",returnValue._Id,returnValue.AvgWeight);
}
And then have a class called Average, where the property name match the names in the BSonDocument, if you want to rename then (because _Id is not so nice in c# terms concerning naming conventions), you can add a $project BsonDocument to your pipeline.
public class Average
{
public string _Id { get; set; }
public Double AvgWeight {get; set; }
}
$project sample (add this in your pipeline just before sort
var project = new BsonDocument
{
{
"$project",
new BsonDocument
{
{"_id", 0},
{"Species","$_id"},
{"AvgWeight", "$AvgWeight"},
}
}
};

Elastic search with Nest

I am working on below code, and what I want to do is query by object itself.
For example: I have a search form, that populates objects fields such as below. Then what I want to do is to search Elastic search based on whatever user filled the form with.
ie: below, I want to query the index by searchItem object. How can I do it easily?
class Program
{
static void Main(string[] args)
{
var p = new Program();
var item1 = new Announcement() {Id=1, Title = "john", ContentText = "lorem", Bar = false, Num = 99, Foo = "hellow"};
//p.Index(item1, "add");
var searchItem = new Announcement() {Title="john",Num=99};
ElasticClient.Search<Announcement>();
Console.Read();
}
public void Index(Announcement announcement, String operation)
{
var uriString = "http://localhost:9200";
var searchBoxUri = new Uri(uriString);
var settings = new ConnectionSettings(searchBoxUri);
settings.SetDefaultIndex("test");
var client = new ElasticClient(settings);
if (operation.Equals("delete"))
{
client.DeleteById("test", "announcement", announcement.Id);
}
else
{
client.Index(announcement, "test", "announcement", announcement.Id);
}
}
private static ElasticClient ElasticClient
{
get
{
try
{
var uriString = "http://localhost:9200";
var searchBoxUri = new Uri(uriString);
var settings = new ConnectionSettings(searchBoxUri);
settings.SetDefaultIndex("test");
return new ElasticClient(settings);
}
catch (Exception)
{
throw;
}
}
}
}
You can't :)
NEST cannot infer how to best query only based on a partially filled POCO. Should it OR or AND should it do a nested term query or a term query wrapped in a has_child? You catch my drift.
Nest does have a slick feature called conditionless queries that allow you the write out to entire query like so:
ElasticClient.Search<Announcement>(s=>s
.Query(q=>
q.Term(p=>p.Title, searchItem.Title)
&& q.Term(p=>p.Num, searchItem.Num)
//Many more queries use () to group all you want
)
)
When NEST sees that the argument passed to Term is null or empty it simply wont render that part of the query.
Read more here on how this feature works http://nest.azurewebsites.net/concepts/writing-queries.html

How to convert List to a string and back

I retrieved a list of users from database, something like
List<User> users = <..list of users from db...>
Name, LastName, DateOfBirth //multidimensional array??
Now I want to store this list as a string and I want be able to reuse it i.e.
string strUsers = users.ToArray().ToString();
How to recreate a list of users from strUsers?
Is it possible?
Use the string.Join method, e.g.
var joined = string.Join(",", users.Select(u => u.Name));
This would give you a single string of user's names separated by ','.
Or for multiple columns:
var joined = string.Join(",",
users.Select(u => u.FirstName + " " + u.LastName ));
You can reverse the process using string.Split, e.g.
var split = joined.Split( new [] {','} );
If you have a lot of users and a lot of columns, it would be better to write your own custom converter class.
public static class UsersConverter
{
// Separates user properties.
private const char UserDataSeparator = ',';
// Separates users in the list.
private const char UsersSeparator = ';';
public static string ConvertListToString(IEnumerable<User> usersList)
{
var stringBuilder = new StringBuilder();
// Build the users string.
foreach (User user in usersList)
{
stringBuilder.Append(user.Name);
stringBuilder.Append(UserDataSeparator);
stringBuilder.Append(user.Age);
stringBuilder.Append(UsersSeparator);
}
// Remove trailing separator.
stringBuilder.Remove(stringBuilder.Length - 1, 1);
return stringBuilder.ToString();
}
public static List<User> ParseStringToList(string usersString)
{
// Check that passed argument is not null.
if (usersString == null) throw new ArgumentNullException("usersString");
var result = new List<User>();
string[] userDatas = usersString.Split(UsersSeparator);
foreach (string[] userData in userDatas.Select(x => x.Split(UserDataSeparator)))
{
// Check that user data contains enough arguments.
if (userData.Length < 2) throw new ArgumentException("Users string contains invalid data.");
string name = userData[0];
int age;
// Try parsing age.
if (!int.TryParse(userData[1], out age))
{
throw new ArgumentException("Users string contains invalid data.");
}
// Add to result list.
result.Add(new User { Name = name, Age = age });
}
return result;
}
}
You will win performance wise using the StringBuilder to build up your users string. You could also easily expand the converter to take account different separators/additional logic etc.
If you need a more generic solution (to be able to use for any class), you could create a converter which uses reflection to iterate over all the public fields, get/set properties to see what can be extracted as string and later reverse the process to convert your string back to the list.
I think what you're looking for is something that lets you dump all users to a string and get the users back from the string, correct?
I suggest something like this:
Add a method that returns an XElement to the Users type:
public XElement GetXElement()
{
return new XElement("User", new XElement("Name", this.FirstName)) //and so on...
}
and then one that decodes the string into a user:
static User GetUserFromXElement(string xml)
{
XElement temp = XElement.Parse(xml);
User temp = new User();
foreach (XElement inner in temp.Elements())
{
switch inner.Name
{
case "Name":
temp.Name = inner.Value
break;
//whatever
}
}
}
And then do this:
public string UsersToElements (List<Users> toWrite)
{
Stringbuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
XElement root = new XElement("root");
XDocument temp = new XDocument(root);
foreach (User user in toWrite)
{
root.Append(user.GetXElement());
}
temp.Save(sw);
return sw.ToString();
}
and this:
public List<Users> ElementsToUsers (string xml)
{
List<Users> usrsList = new List<Users>();
XDocument temp = XDocument.Load(xml);
foreach (XElement e in XDocument.Root.Elements())
{
usrsList.Append(Users.GetUserFromXElement(e));
}
return usrsList;
}
JSON solution (using JSON.NET)
public JObject GetJObject()
{
return new JObject("user", new JProperty("name", this.FirstName)); //so on
}
static User GetUserFromJObject(string json)
{
JObject obj = JObject.Parse(json);
return new User() { FirstName = (string)obj["user"]["name"] }; //so on
}
public string UsersToElements (List<Users> users)
{
JObject root = new JObject(from usr in users select new JAttribute("user", usr.GetJObject());
return root.ToString();
}
public List<users> ElementsToUsers(string json)
{
List<Users> users = new List<Users>();
JObject temp = JObject.Parse(json);
foreach (JObject o in (JEnumerable<JObject>)temp.Children())
{
users.Add(Users.GetUserFromJObject(o.ToString());
}
return users;
}
I have no idea if ths works :/ (well the XML I know it does, not so sure about the JSON)
Use this code
string combindedString = string.Join( ",", myList );
var Array = combindedString.Split( new [] {','} );

Categories