Linq exception on Provider.CreateQuery - c#

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);

Related

Dynamic Linq Select from List<IDictionary<string, object>>

Consider I have this IQueryble database collection which is similar to this list List and linq:
var lstData = new List<IDictionary<string, object>>()
{
new Dictionary<string, object>() {
{ "Name" , "John"},
{ "Age", 20 },
{ "School", "Waterloo" }
},
new Dictionary<string, object>() {
{ "Name" , "Goli"},
{ "Age", 23 },
{ "School", "Mazandaran" }
},
};
var result = lstData.Select(x => new { Name= x["Name"], School= x["School"] });
However, I do not know the name of properties at compilation. How do I dynamically select specific columns in the Linq select at runtime? something like this:
var result= lstData .Select("Name,School");
It would be more efficient and more maintainable if you define a class that you could store your data for each person rather than storing it in an object which in that case it would need unnecessary boxing and unboxing every time you search through your list. having that said you could search in your data structure like this:
var result = lstData.Find(x => x["Name"] == (object)"John" && x["School"] == (object)"Waterloo");
var age = (int)result["Age"]; //20
Edit
If the goal is to get properties as an anonymous list without knowing the property names at compilation time it's not possible. the anonymous type in C# needs the property name first hand or at least property access when you define it. an alternative way to solve this problem would be to use another dictionary so you could map the original one into:
var summayList = lstData.Select(x => new Dictionary<string, object>()
{
{ "Name", x["Name"] },
{ "School", x["School"] }
});
var john = summayList.Single(x => x["Name"] == (object)"John");
if the number of properties is unknown in addition to their names, you could use a method that return a list of dictionaries given unknown number of property names:
public static IEnumerable<IDictionary<string, object>> GetProps(List<IDictionary<string, object>> list, params string[] props)
{
return list.Select(x => new Dictionary<string, object>(
props.Select(p => new KeyValuePair<string, object>(p, x[p]))));
}
Usage
var result = GetProps(lstData, "Name", "School");
var goli = result.Single(x => x["Name"] == (object)"Goli");
I have finished the code, try the following code.
Func<IDictionary<string, object>, dynamic> CreateDynamicFromDict(string fields)
{
// input parameter "x"
var xParameter = Expression.Parameter(typeof(IDictionary<string, object>), "x");
// output object
var result = Expression.Parameter(typeof(IDictionary<string, object>), "result");
var add = typeof(IDictionary<string, object>).GetMethod("Add");
var body = new List<Expression>();
//initial output object
body.Add(Expression.Assign(result, Expression.New(typeof(ExpandoObject))));
// set value "FieldN = x.FieldN"
var bindings = fields.Split(',').Select(o => o.Trim())
.Select(o =>
{
var key = Expression.Constant(o);
return Expression.Call(result, add, key, Expression.Property(xParameter, "Item", key));
});
body.AddRange(bindings);
// return value
body.Add(result);
var block = Expression.Block(new[] { result }, body);
var lambda = Expression.Lambda<Func<IDictionary<string, object>, dynamic>>(block, xParameter);
// compile to Func<IDictionary<string, object>, dynamic>
return lambda.Compile();
}
The usage is
var result = lstData.Select(CreateDynamicFromDict("Field1, Field2"));
References:
LINQ : Dynamic select
Expression tree create dictionary with property values for class
Expression Trees (C#)
Expression Tree

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 do I represent an "empty object" for MongoDB .NET Driver?

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())) };

How to create linq WHERE IN statement without using Contains()

I have a situation where I am using a linq provider that does not support the .Contains method to generate a WHERE IN clause in the query. I am looking for a way to generate the (Value = X OR Value = Y OR Value = Z) statement dynamically from the list of items to match. I haven't found a good example of building expression trees to do this.
Normally I would query this way:
var names = new string[] { "name1", "name2", "name3" }
var matches = query.Where(x => names.Contains(x.Name));
So far the closest thing I could find was to use the
Dynamic Linq Library and build a string to be interpreted but it feels a bit too hacky.
You don't even need an external library for something like this:
var names = new string[] { "name1", "name2", "name3" };
// Where MyClass is the type of your class
ParameterExpression par = Expression.Parameter(typeof(MyClass));
MemberExpression prop = Expression.Property(par, "Name");
Expression expression = null;
foreach (string name in names)
{
Expression expression2 = Expression.Equal(prop, Expression.Constant(name));
if (expression == null)
{
expression = expression2;
}
else
{
expression = Expression.OrElse(expression, expression2);
}
}
var query = ...; // Your query
if (expression != null)
{
// Where MyClass is the type of your class
var lambda = Expression.Lambda<Func<MyClass, bool>>(expression, par);
query = query.Where(lambda);
}
You can build a concatenation of Expression.OrElse, with the comparisons between the property Name and one of the strings.
In this particular case (3 strings), the resulting Expression, when looked from the debugger, is:
(((Param_0.Name == "name1") OrElse (Param_0.Name == "name2")) OrElse (Param_0.Name == "name3"))
Just improving the answer by xanatos:
var names = new string[] { "name1", "name2", "name3" };
var query = ...; // Your query
if (names.Any())
{
// Where MyClass is the type of your class
ParameterExpression par = Expression.Parameter(typeof(MyClass));
MemberExpression prop = Expression.Property(par, "Name");
var expression=names
.Select(v => Expression.Equal(prop, Expression.Constant(v)))
.Aggregate(Expression.OrElse);
// Where MyClass is the type of your class
var lambda = Expression.Lambda<Func<MyClass, bool>>(expression, par);
query = query.Where(lambda);
}

Categories

Resources