How do I represent an "empty object" for MongoDB .NET Driver? - c#

I am trying to retrieve index statistics using the MongoDB .NET Driver.
I have tried the following variations of my pipeline
var statsPipeline = new[] { new BsonDocument(new BsonElement("$indexStats", BsonNull.Value)) };
var statsPipeline = new[] { new BsonDocument { {"$indexStats", "" } } };
var statsPipeline = new[] { new BsonDocument { {"$indexStats", null } } };
var statsPipeline = new[] { new BsonDocument { {"$indexStats", BsonNull.Value } } };
var statsPipeline = new[] { new BsonDocument { {"$indexStats", "{ }"} } };
which is passed to the query
var stats = await db
.GetCollection<BsonDocument>("CollectionName")
.AggregateAsync<BsonDocument>(statsPipeline);
With the exception of the one containing null, which resulted in an ArgumentNullException, I have received the exception
MongoDB.Driver.MongoCommandException: Command aggregate failed: The $indexStats stage specification must be an empty object.
How do I change my query such that the $indexStats stage specification is indeed an empty object?

Ok, this one worked:
var statsPipeline = new[] { new BsonDocument(new BsonElement("$indexStats", new BsonDocument())) };

Related

MongoDb - cursor option is required after upgrade of mongodb

Since we were forced to upgrade our mongo installation, we're receiving an error during some aggregation function calls:
MongoDB.Driver.MongoCommandException: "Command 'aggregate' failed: The
'cursor' option is required, except for aggregate with the explain
argument (response: { "ok" : 0.0, "errmsg" : "The 'cursor' option is
required, except for aggregate with the explain argument", "code" : 9,
"codeName" : "FailedToParse" })"
BsonArray arr = BsonSerializer.Deserialize<BsonArray>("[{ \"$match\" : { \"Param1\" : \"VAL\" } }, { \"$unwind\" : \"$Entries\" }, { \"$match\" : { \"PARAM\" : \"VALUE\" } }]");
var pipeline = arr.Select(x => x.AsBsonDocument).ToList();
// AggregateArgs aArgs = new AggregateArgs { Pipeline = bsonList };
var cursor = collection.Aggregate(pipeline).ResultDocuments;
I already figured out, that we have to manually add cursor configuration to the BsonDocument - but we weren't able to figure out, how the query should be configured.
Is there any work around for this exception (without changing drivers)?
give this a shot:
var cursor = collection.Aggregate<BsonDocument>(pipeline);
var results = cursor.ToList(); //just get a list of documents and be done with it
while (cursor.MoveNext()) // or iterate over cursor
{
foreach (var doc in cursor.Current.ToArray())
{
//access your documents here
}
}
You have extra brace in the end of query string
was finally able to fix it, by building the command by myself:
var cmd = new CommandDocument()
{
{"aggregate", "collection_name" },
{"pipeline", arr},
{"cursor", BsonDocument.Parse("{}") }
};
var res = db.RunCommand(cmd);
This is what worked in my situation (mongocshardriver v1.9.0-rc0, mongodb server 4.4.0); OutputMode = AggregateOutputMode.Cursor in the AggregateArgs.
public IEnumerable<BsonDocument> Run(MongoCollection<Item> items)
{
var priceRange = new BsonDocument(
"$subtract",
new BsonArray
{
"$Price",
new BsonDocument(
"$mod",
new BsonArray{"$Price", 100})
});
var grouping = new BsonDocument(
"$group",
new BsonDocument
{
{"_id", priceRange},
{"count", new BsonDocument("$sum", 1)}
});
var sort = new BsonDocument(
"$sort",
new BsonDocument("_id", 1)
);
var args = new AggregateArgs
{
Pipeline = new[] { grouping, sort },
OutputMode = AggregateOutputMode.Cursor,
};
return items.Aggregate(args);
}

How to convert list to expandobject key in c#?

I have a function in which i am getting data(array of objects) from db and then adding those objects of array one by one into a lit of type ExpandoObject
public async Task<List<ExpandoObject>> GetGroupWithMaxTickets(){
List<ExpandoObject> topGroupsWithMaxTickets = new List<ExpandoObject>();
dynamic ticketDetails = new ExpandoObject();
var pipeline_tickets = new BsonDocument[]{
new BsonDocument("$match",
new BsonDocument
{
{ "nsp", "/sbtjapan.com" },
{ "datetime",
new BsonDocument
{
{ "$gte", "2019-12-03T00:00:34.417Z" },
{ "$lte", "2019-12-03T24:00:34.417Z" }
} }
}),
new BsonDocument("$group",
new BsonDocument
{
{ "_id", "$group" },
{ "totalTIckets",
new BsonDocument("$sum", 1) }
}),
new BsonDocument("$project",
new BsonDocument
{
{ "_id", 0 },
{ "group", "$_id" },
{ "totalTIckets", 1 }
}),
new BsonDocument("$sort",
new BsonDocument("totalTIckets", -1)),
new BsonDocument("$limit", 5)
};
var collection = await DbService.tickets.AggregateAsync<RawBsonDocument>(pipeline_tickets, new AggregateOptions {UseCursor = true, BatchSize = 500});
await collection.MoveNextAsync();
if(collection.Current.ToList().Count > 0){
// ticketDetails = JsonConvert.DeserializeObject(collection.Current.ToJson());
// ticketDetails.group = collection.Current.ToList()[0]["group"];
// ticketDetails.totalTickets = collection.Current.ToList()[0]["totalTIckets"];
Parallel.ForEach(collection.Current.ToList(), (ticket) => {
Console.WriteLine("Ticket----"+ticket);
dynamic groupWithTickets = new ExpandoObject();
groupWithTickets = ticket;
topGroupsWithMaxTickets.Add(groupWithTickets);
});
}
return topGroupsWithMaxTickets;
}
But it throws an error like this
System.AggregateException: One or more errors occurred. (The best overloaded method match for 'System.Collections.Generic.List<System.Dynamic.ExpandoObject>.Add(System.Dynamic.ExpandoObject)' has some invalid arguments)
I want that my function must return array of objects of type List<ExpandoObject>
How can i do this in c#?
Since you have changed the question, following is the answer that should resolve your matters.
How to NOT work with ExpandoObjects
I tested this on my system and got it to reproduce the same results as you are getting. Following is the failed try:
dynamic employee = new ExpandoObject();
List<ExpandoObject> listOfEmployees = new List<ExpandoObject>();
employee = "someStrangeString";
listOfEmployees.Add(employee); // ERROR !!!!
and just as expected, i get the following error on Add.
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
HResult=0x80131500
Message=The best overloaded method match for 'System.Collections.Generic.List.Add(System.Dynamic.ExpandoObject)' has some invalid arguments
Source=
StackTrace:
Corrected way of ExpandoObject use
Following is the method that will take care of the issues with Adding it to the list.
Parallel.ForEach(collection.Current.ToList(), (ticket) =>
{
Console.WriteLine("Ticket----" + ticket);
dynamic groupWithTickets = new ExpandoObject();
groupWithTickets.users = ticket; //<---- Assign ticket to users element.
topGroupsWithMaxTickets.Add(groupWithTickets);
});
What was done to fix it?
When you are working with ExpandoObjects, you have to think of dictionary type of a deal. When you declare ExpandoObject, you have to dynamically assign the value to an element (that you define).
Example from MS site: shows the proper use of ExpandoObject
dynamic employee, manager;
employee = new ExpandoObject();
employee.Name = "John Smith";
employee.Age = 33;
manager = new ExpandoObject();
manager.Name = "Allison Brown";
manager.Age = 42;
manager.TeamSize = 10;
Hopefully this resolves your issue.
It should be like this;
dynamic ticketDetails = new ExpandoObject();
ticketDetails.user = collection;
string json = Newtonsoft.Json.JsonConvert.SerializeObject(ticketDetails);
You can simply do this:
dynamic ticketDetails = new ExpandoObject();
ticketDetails = Json(new users = JsonConvert.DeserializeObject(collection.Current.ToJson()));
For testing purposes, i used an array arr that holds one of the elements. If you need that array to be part of ExtendoObject with first element being users, you can create a Json object and set the array as value to the "users" element.
dynamic ticketDetails = new ExpandoObject();
JArray arr = new JArray();
arr.Add(#"[{""name"": ""Alex"", ""age"": 21}]");
JObject o = new JObject();
o["users"] = arr.ToString();
ticketDetails = o;
// output: { "users" : [{"name" : "Alex", "age" : 21}]}

how to form comma separated string from bson array using asp.net core 2.2?

I have an api based on asp.net core 2.2 in which i am building up an array of ips(strings type) like this
[HttpGet ("{nsp}/geolocation")]
[ResponseCache (Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public async Task<dynamic> getLocation (string nsp) {
nsp = "/"+nsp;
// string ipInfoBaseUrl = "http://ip-api.com/json/";
string baseUrl = "http://ip-api.com/batch";
// string userIpAddress = "197.157.194.90";
// string ipUrl = ipInfoBaseUrl + userIpAddress;
// var client = _httpClientFactory.CreateClient();
// var result = await client.PostAsync(baseUrl,new StringContent(JsonConvert.SerializeObject(finals), System.Text.Encoding.UTF8, "application/json"));
// // var final = Newtonsoft.Json.JsonConvert.DeserializeObject<UserLocation>(result);
// Console.WriteLine(finals+" --result-----");
// return Ok(result);
var match = new BsonDocument ();
var group = new BsonDocument ();
var project = new BsonDocument ();
var sort = new BsonDocument ();
var addFields = new BsonDocument ();
var pipeline = new [] { new BsonDocument () };
/* #Aggregation : Stage-1 */
match = new BsonDocument ("$match",
new BsonDocument {
{
"nsp" , nsp
}
});
/* #Aggregation : Stage-2 */
group = new BsonDocument("$group",
new BsonDocument
{ {
"_id", "null"
},
{ "geoLocations",
new BsonDocument("$addToSet", "$visitor.ip")
}
});
/* #Aggregation : Stage-3 */
project = new BsonDocument ("$project", new BsonDocument { { "_id", 0 }});
pipeline = new [] { match, group,project};
var list = await DbService.tickets.AggregateAsync<BsonDocument> (pipeline, new AggregateOptions { UseCursor = true, BatchSize = batchCount });
while (await list.MoveNextAsync ()) {
var list_real = new List<BsonValue> ();
foreach (var data in list.Current.ToArray ()) {
list_real.Add (data);
}
return list_real.ToJson ();
}
return new BsonArray ().ToJson ();
}
It is returning result like this
[
{
" geoLocations": [
"122.8.208.9",
"196.62.107.243",
"182.188.38.219",
"39.50.244.198",
"39.51.40.251",
"103.20.134.56",
"103.228.156.83",
"202.143.125.21",
"196.62.151.47",
"45.116.232.50",
"39.57.128.75",
"103.18.8.60",
"202.143.125.20",
"182.190.252.96",
"119.153.56.2",
"46.101.89.227",
"196.194.172.211",
"192.168.20.186",
"64.233.173.146",
"104.236.195.147",
"39.50.156.242",
"103.255.5.58"
]
}
]
How can i get comma separated string from this result like
"111.92.158.82","202.142.168.162","122.8.157.172",.....
From very first i am getting all ips from all documents from my collection and forming an array of ips.But my ultimate goal is to form a comma separated string from that array because i have to pass that comma separated string of ips into an api to get ips locations.
I am using asp.net core and c#. How can i achieve this?
Assuming you want a single comma-separated result string containing all IP addresses, replace the method signature with IEnumerable<string>, and replace the bottom of the method with. Just use whatever you need for your result and get rid of the rest.
var list = await DbService.tickets.AggregateAsync<BsonDocument> (pipeline,
new AggregateOptions
{
UseCursor = true,
BatchSize = batchCount
});
var result = new List<string>();
while (await list.MoveNextAsync())
result.AddRange(list.Current.Cast<string>());
return string.Join(',', result);
I'm not sure why you're doing everything with BsonDocuments, you can just iterate over the data directly and return strings.
Also, consider upgrading to .NET Core 3, which you can then use C# 8's async enumerable. You'll also be able to use the new JSON functionality built-in .NET Core 3.
This will return:
"122.8.208.9,196.62.107.243,182.188.38.219,<all the rest>"

Linq exception on Provider.CreateQuery

Given this class:
class SomeClass
{
public int SomeValue { get; set; }
}
The following list:
var queryableData = new List<SomeClass>() {
new SomeClass{SomeValue=1 },
new SomeClass{SomeValue=2 },
new SomeClass{SomeValue=3 },
new SomeClass{SomeValue=4 },
new SomeClass{SomeValue=5 },
new SomeClass{SomeValue=6 },
new SomeClass{SomeValue=7 },
}.AsQueryable();
And this code where i try to dinamically query the list (note that the string query could contain anything such as Take,Select,OrderBy, etc).
var externals = new Dictionary<string, object>();
externals.Add("SomeClass", queryableData);
string query = "SomeClass.Where(o => o.SomeValue >= 3)"; // or any query
// here i use the code from System.Linq.Dynamic
var expression = DynamicExpression.Parse(typeof(IQueryable<SomeClass>), query, new[] { externals });
// result will have five values
var result = queryableData.Provider.CreateQuery<SomeClass>(expression);
// here i use the code from System.Linq.Dynamic.Core
var expression2 = DynamicExpressionParser.ParseLambda(typeof(IQueryable<SomeClass>), query, new[] { externals });
// will throw exception here: Argument expression is not valid
var result2 = queryableData.Provider.CreateQuery<SomeClass>(expression2).ToDynamicArray();
I want to know what am i doing wrong that the Provider.CreateQuery is thowing the "Argument expression is not valid" exception?
The difference with DynamicExpression.Parse (hence the cause of the problem) is that the DynamicExpressionParser.ParseLambda method returns LambdaExpression (basically Expression<Func<TResult>>) which is not a valid query expression.
But the Body of it is, so the simplest fix is to use
var result2 = queryableData.Provider.CreateQuery<SomeClass>(expression2.Body);
Alternatively you could use System.Linq.Dynamic.Core.Parser.ExpressionParser class directly:
var expression2 = new ExpressionParser(null, query, new[] { externals }, null)
.Parse(typeof(IQueryable<SomeClass>));
var result2 = queryableData.Provider.CreateQuery<SomeClass>(expression2);

How do I compose a date range query in Mongodb's aggregation framework?

My goal is to perform a match and group based on date range using mongodb 2.2 and latest c# driver.
I'm having trouble creating a match query in the aggregation framework that uses datetimes. I dont know if my datatype is incorrect (the poco has a datetime and the object was utc at save) or if the query is incorrect, which is more likely.
//Here is the doc format that I'm querying:
{
"DateTime" : new Date("2/20/2013 17:29:03"),
"DateTimeString" : "20130220",
"PurchaseAmount" : 1.91,
"ProductId" : "51293ac844da932e941fa2c4",
"_id" : "20130220/51293ac844da932e941fa2c4"
}
//and here is the aggregation query i'm attempting
var timeStamp = DateTime.UtcNow.AddDays(-20);
var startDate = timeStamp.Date;
var endDate = timeStamp.AddDays(1).Date;
var match = new BsonDocument
{
{
"$match",
new BsonDocument
{
{ "ProductId" , "51293ac844da932e941fa2c4"}
}
}
};
var match2 = new BsonDocument
{
{
"$match",
new BsonDocument
{
*** { "$gte" , new BsonDocument{{"DateTime", timeStamp}} } //,
*** // { "$lt" , endDate}
}
}
};
var group = new BsonDocument
{
{ "$group",
new BsonDocument
{
{ "_id", new BsonDocument
{
{ "ProductId" , "$ProductId"}
,{ "DateTime" , "$DateTime" }
}
},
{
"AvgOrder", new BsonDocument
{
{ "$avg", "$PurchaseAmount" }
}
}
}
}
};
var pipeline = new[] { match, match2, group };
var aggregateResult = dbSession.GetCollection<RecentOrders>().Aggregate(pipeline);
Please see the *. I want to provide specific time ranges and calculate the average orders for that customer over that interval (by minute, hour, or day ranges). I believe that I have it working except for the date range query portion (match2). I think the issue is the tricky combination of new BsonDocuments and the query. Thanks so much if you can help!
You've got things swapped around in match2. It should be like this instead:
var match2 = new BsonDocument
{{ "$match", new BsonDocument
{{ "DateTime", new BsonDocument {
{"$gte", timeStamp},
{"$lt", endDate}
}}}
}};
another syntax without too many curly braces
var match2 = new BsonDocument()
.Add("$match",
new BsonDocument().Add("TimeStamp",
new BsonDocument().Add("$gte", timestamp)
.Add("$lte", end)));

Categories

Resources