Using the old MongoDB driver, I was able to perform the following query:
Query.Where("this.plan.sstats.used < this.plan.sstats.available")
But with the new one, I'm forced to write this:
builder.Filter
.Where(t => t.Plan.StorageStats.UploadUsed < t.Plan.StorageStats.UploadAvailable)
These look the same, but the new one does not work and I receive this error message:
Additional information: Unsupported filter: ({plan.sstats.used} <
{plan.sstats.available}).
The back-end version is currently the same, so I don't see any reason why this shouldn't continue to be able to work.
How do I fix this? Is there a better way of doing this, whilst maintaining atomicity?
Seems MongoDb drive doesn't support it any more. Me personally see two possible solutions:
1) You query bson, not your objects, that should work (i have tried with my sample data out):
FilterDefinition<BsonDocument> filter =
new BsonDocument("$where", "this.plan.sstats.used<this.plan.sstats.available");
Waht is bad on this approach: your should query your collection as BsonDocument collection.
2) You query your collection as ToEnumerable() and than just add your filter as Where linq statement. That will work too, but you loose querying data directly on mongodb.
3) You could use aggregation framework, i did it this way:
var result = collection.Aggregate()
.Group(r => r.Plan.StorageStats.UploadUsed - r.Plan.StorageStats.UploadAvailable,
r => new {r.Key, Plans= r.Select(t=>t.Plan)} )
.Match(r=>r.Key < 0)
.ToEnumerable()
.SelectMany(r=>r.Plans);
Negative on aggregate is that you couldn't combine it with your other Filters you use in Find() call.
So, I'd also asked over on Mongo's JIRA and was given this as a potential alternative. I'm posting it here in case anyone is dissatisfied with Maksim's answer.
It's possible to create just a filter definition:
FilterDefinition<C> filter = new JsonFilterDefinition<C>("{ $where : \"this.plan.sstats.used < this.plan.sstats.available\" }");
Since strings can be converted to FilterDefinitions you could also write either of the following which both end up creating a JsonFilterDefinition: var filter = (FilterDefinition<C>)"{ $where : \"this.plan.sstats.used < this.plan.sstats.available\" }";
// or using an implicit conversion
FilterDefinition<C> filter = "{ $where : \"this.plan.sstats.used < this.plan.sstats.available\" }";
Related
I have been trying to pass in a dynamic list of Expressions to a MongoDB C# Driver query using Linq ... This method works for me with regular Linq queries against an ORM, for example, but results in an error when applied to a MongoDB query ... (FYI: I am also using LinqKit's PredicateBuilder)
//
// I create a List of Expressions which I can then add individual predicates to on an
// "as-needed" basis.
var filters = new List<Expression<Func<Session, Boolean>>>();
//
// If the Region DropDownList returns a value then add an expression to match it.
// (the WebFormsService is a home built service for extracting data from the various
// WebForms Server Controls... in case you're wondering how it fits in)
if (!String.IsNullOrEmpty(WebFormsService.GetControlValueAsString(this.ddlRegion)))
{
String region = WebFormsService.GetControlValueAsString(this.ddlRegion).ToLower();
filters.Add(e => e.Region.ToLower() == region);
}
//
// If the StartDate has been specified then add an expression to match it.
if (this.StartDate.HasValue)
{
Int64 startTicks = this.StartDate.Value.Ticks;
filters.Add(e => e.StartTimestampTicks >= startTicks);
}
//
// If the EndDate has been specified then add an expression to match it.
if (this.EndDate.HasValue)
{
Int64 endTicks = this.EndDate.Value.Ticks;
filters.Add(e => e.StartTimestampTicks <= endTicks);
}
//
// Pass the Expression list to the method that executes the query
var data = SessionMsgsDbSvc.GetSessionMsgs(filters);
The GetSessionMsgs() method is defined in a Data services class ...
public class SessionMsgsDbSvc
{
public static List<LocationOwnerSessions> GetSessionMsgs(List<Expression<Func<Session, Boolean>>> values)
{
//
// Using the LinqKit PredicateBuilder I simply add the provided expressions
// into a single "AND" expression ...
var predicate = PredicateBuilder.True<Session>();
foreach (var value in values)
{
predicate = predicate.And(value);
}
//
// ... and apply it as I would to any Linq query, in the Where clause.
// Additionally, using the Select clause I project the results into a
// pre-defined data transfer object (DTO) and only the DISTINCT DTOs are returned
var query = ApplCoreMsgDbCtx.Sessions.AsQueryable()
.Where(predicate)
.Select(e => new LocationOwnerSessions
{
AssetNumber = e.AssetNumber,
Owner = e.LocationOwner,
Region = e.Region
})
.Distinct();
var data = query.ToList();
return data;
}
}
Using the LinqKit PredicateBuilder I simply add the provided expressions into a single "AND" expression ... and apply it as I would to any Linq query, in the Where() clause. Additionally, using the Select() clause I project the results into a pre-defined data transfer object (DTO) and only the DISTINCT DTOs are returned.
This technique typically works when I an going against my Telerik ORM Context Entity collections ... but when I run this against the Mongo Document Collection I get the following error ...
Unsupported filter: Invoke(e => (e.Region.ToLower() == "central"),
{document})
There is certainly something going on beneath the covers that I am unclear on. In the C# Driver for MongoDB documentation I found the following NOTE ...
"When projecting scalars, the driver will wrap the scalar into a
document with a generated field name because MongoDB requires that
output from an aggregation pipeline be documents"
But honestly I am not sure what that neccessarily means or if it's related to this problem or not. The appearence of "{document}" in the error suggests that it might be relevant though.
Any additional thoughts or insight would be greatly appreciated though. Been stuck on this for the better part of 2 days now ...
I did find this post but so far am not sure how the accepted solution is much different than what I have done.
I'm coming back to revisit this after 4 years because while my original supposition did work it worked the wrong way which was it was pulling back all the records from Mongo and then filtering them in memory and to compound matters it was making a synchronous call into the database which is always a bad idea.
The magic happens in LinqKit's expand extension method
That flattens the invocation expression tree into something the Mongo driver can understand and thus act upon.
.Where(predicate.Expand())
Is there any way to use the LINQ dynamic query library (System.Linq.Dynamic) to evaluate a condition based on the properties of an ExpandoObject? The following code throws an exception on the "var e..." line, saying "No property or field 'Weight' exists in type ExpandoObject":-
const string TestCondition = "MyStateBag.Foo >= 50 && MyStateBag.Bar >= 100";
dynamic myStateBag = new ExpandoObject();
myStateBag.Foo = 70;
myStateBag.Bar = 100;
var p = Expression.Parameter(typeof(ExpandoObject), "MyStateBag");
var e = DynamicExpression.ParseLambda(new[] { p }, null, TestCondition);
var result = e.Compile().DynamicInvoke(myStateBag);
Assert.IsTrue(result);
The alternative would be to implement the "statebag" as a dictionary, but this will result in a slightly more verbose condition string, e.g. MyStateBag["Foo"] >= 50 && MyStateBag["Bar"] >= 100. As this is going to be used as the basis of a user scripting environment, I would prefer the simpler ExpandoObject syntax if it's possible to achieve.
Not directly. The dynamic LINQ library boils down to an expression-tree, and expression trees do not directly support dynamic. Most likely, the dynamic query library is using Expression.PropertyOrField to handle .Foo etc, and that will not work with dynamic.
You could perhaps write a custom expression parser that replaces this with lots of lookup code if it finds the parameter is a dictionary; not fun, though.
Using the MongoDB C# driver How can I include more than one field in the query (Im using vb.net)
I know how to do (for name1=value1)
Dim qry = Query.EQ("name1","value1")
How can I modify this query so I can make it find all documents where name1=value1 and name2=value2?
( Similar to )
db.collection.find({"name1":"value1","name2":"value2"})
I wanted to search a text in different fields and Full Text Search doesn't work for me even after wasting so much time. so I tried this.
var filter = Builders<Book>.Filter.Or(
Builders<Book>.Filter.Where(p=>p.Title.ToLower().Contains(queryText.ToLower())),
Builders<Book>.Filter.Where(p => p.Publisher.ToLower().Contains(queryText.ToLower())),
Builders<Book>.Filter.Where(p => p.Description.ToLower().Contains(queryText.ToLower()))
);
List<Book> books = Collection.Find(filter).ToList();
You can use:
var arrayFilter = Builders<BsonDocument>.Filter.Eq("student_id", 10000)
& Builders<BsonDocument>.Filter.Eq("scores.type", "quiz");
Reference: https://www.mongodb.com/blog/post/quick-start-csharp-and-mongodb--update-operation
And doesn't always do what you want (as I found was the case when doing a not operation on top of an and). You can also create a new QueryDocument, as shown below. This is exactly the equivalent of what you were looking for.
Query.Not(new QueryDocument {
{ "Results.Instance", instance },
{ "Results.User", user.Email } }))
Hi I am trying to get my head around grouping, and then building my own class in the result. I know the result of a group by is an IGrouping collection but can I access the rows as they are being built to add a couple of flags to them with a custom class?
I have a class called FlightTimes with some data, but I'd like to append some data to the rows, like a FlagRedEye. So I created a class called FlightTimeResult with the original FlightTime class data plus the flag.
Can I do this? I can't seem to figure out how to get it to work. I like to use strong types until I understand what is going on. I had to change a few things to protect my client so I apologize for any syntax errors.
IGrouping<string, FlightTimeResult> FlightTimes =
( from flighttimes in schedules.FlightTimes
group flighttimes by flighttimes.FlightType.ToString()
into groupedFlights
select new FlightTimeResult( )
{
FlightTimeData = FlightTime, // Original class data
FlagRedEye = (FlightTime.departureTime.Hour >= 0 &&
FlightTime.departureTime.Hour < 6) // Extra flag
} )
The goal is to have a collection of FlightTimesResult (FlightTime + extra flag) grouped by FlightType. Not sure how to access the individual FlightTime rows in the query 'select new FlightTimeResult()'
Do i need to use a nested query on the groupedFlights?
Thank you very much.
It is easiest achieved by calling Linq functions explicitly in following way:
IQueryable<IGrouping<string, FlightTimeResult>> query
= schedules.FlightTimes.GroupBy(
ft => ft.FlightType.ToString(), // key
ft => new FlightTimeResult() { // your constructed objects for key
FlightTimeData = ft,
FlagRedEye = (ft.departureTime.Hour >= 0 && ft.departureTime.Hour < 6)
}
);
The two-argument GroupBy operator function takes two lambdas as arguments - one for extracting keys, second for extracting values for it.
Also keep in mind that group by operation (be it group itm by key construction or GroupBy call) returns a collection of IGrouping<,>s - not a single one.
Thus it will be IEnumerable<IGrouping<,>> or IQueryable<IGrouping<,>>.
I think you're on the right track. Instead of grouping FlightTimes by FlightType, try building FlightTimeResults and grouping those by FlightType instead:
var results =
from ft in schedules.FlightTimes
group new FlightTimeResult
{
FlightTimeData = ft,
FlagRedeye = ft.DepartureTime.Hour >= 0 && ft.DepartureTime.Hour < 6
}
by ft.FlightType.ToString()
into groupedFlights
select groupedFlights;
I am just doing some experiments on Castle AR and 2nd level cache of NH. In the following two methods, I can see caching working fine but only for the repetition of the call of each. In other words if I call RetrieveByPrimaryKey twice for same PK, the object is found in cache. And if I call RetrieveAll twice, I see SQL issued only once.
But if I call RetrieveAll and then RetrieveByPrimaryKey with some PK, I see two SQL statements getting issued. My question is, Why AR does not look for that entity in cache first? Sure it would have found it there as a result of previous call to RetrieveAll.
public static T RetrieveByPrimaryKey(Guid id)
{
var res = default(T);
var findCriteria = DetachedCriteria.For<T>().SetCacheable(true);
var eqExpression = NHibernate.Criterion.Expression.Eq("Id", id);
findCriteria.Add(eqExpression);
var items = FindAll(findCriteria);
if (items != null && items.Length > 0)
res = items[0];
return res;
}
public static T[] RetrieveAll()
{
var findCriteria = DetachedCriteria.For<T>().SetCacheable(true);
var res = FindAll(findCriteria);
return res;
}
You're using caching on specific queries. that means that cache lookup is done in the following way:
search the cahce for results of a query with identical syntax AND the same parameters. If found- use cached results.
nHibernate (this has nothing to do with AR, by the way) doesn't know that logically, one query 'contains' the other. so this is why you're getting 2 db trips.
I would suggest using ISession.Get to retreive items by ID (it's the recommended method). I think (not tested it though) that Get can use items cached by other queries.
here's a nice blog post from ayende about it.