Is there an "Explain Query" for MongoDB Linq? - c#

Is there a way to run .explain() or equivalent on Linq queries? I would want to know
The text of the actual JSON query
The output of .explain() (indexes used, etc)
It would also be nice to have the execution time of the query

You can get the Json easily enough if you have a query wrapper;
var qLinq = Query<T>.Where(x => x.name=="jim");
Console.WriteLine(qLinq.ToJson());
There's also an Explain() method on MongoCursor, so you could do this;
var exp = Collection.FindAs<T>(qLinq).Explain()
Console.WriteLine(exp.ToJson());
So if you want the time taken, "millis" is in there;
var msTaken = exp.First(x => x.Name == "millis").Value.AsInt32;
If you have an IQueryable, try something like this;
void Do(MongoCollection col, IQueryable iq)
{
// Json Mongo Query
var imq = (iq as MongoQueryable<Blob>).GetMongoQuery();
Console.WriteLine(imq.ToString());
// you could also just do;
// var cursor = col.FindAs(typeof(Blob), imq);
var cursor = MongoCursor.Create(typeof(Blob), col, imq, ReadPreference.Nearest);
var explainDoc = cursor.Explain();
Console.WriteLine(explainDoc);
}//Do()

If you want this functionality in a library, I just created a GitHub project entitled
MongoDB query helper for .NET
https://github.com/mikeckennedy/mongodb-query-helper-for-dotnet
It will:
Explain a LINQ query as a strongly typed object (does it use an index for example)
Convert a LINQ query to the JavaScript code run in MongoDB
Check it out and contribute if you find it interesting.

Yes, there is. It shows everything .explain does and has a boolean for verbosity (it includes the time it took to execute):
var database = new MongoClient().GetServer().GetDatabase("db");
var collection = database.GetCollection<Hamster>("Hamsters");
var explanation = collection.AsQueryable().Where(hamster => hamster.Name == "bar").Explain(true);
Console.WriteLine(explanation);
It doesn't show the query though. Here's an extension method for that:
public static string GetMongoQuery<TItem>(this IQueryable<TItem> query)
{
var mongoQuery = query as MongoQueryable<TItem>;
return mongoQuery == null ? null : mongoQuery.GetMongoQuery().ToString();
}
Usage:
var query = collection.AsQueryable().Where(hamster => hamster.Name == "bar").GetMongoQuery();
Console.WriteLine(query);

In mongodb 3 C# I used following:
var users = Mongo.db.GetCollection<User>("Users");
var r = users(m => m._id == yourIdHere)
.Project(m => new { m._id, m.UserName, m.FirstName, m.LastName })
.Limit(1);
Console.WriteLine(r.ToString());
Result:
find({ "_id" : ObjectId("56030e87ca42192008ed0955") }, { "_id" : 1, "UserName" : 1, "FirstName" : 1, "LastName" : 1 }).limit(1)

Related

Linq - EntityFramework NotSupportedException

I have a query that looks like this:
var caseList = (from x in context.Cases
where allowedCaseIds.Contains(x => x.CaseId)
select new Case {
CaseId = x.CaseId,
NotifierId = x.NotifierId,
Notifier = x.NotifierId.HasValue ? new Notifier { Name = x.Notifier.Name } : null // This line throws exception
}).ToList();
A Case class can have 0..1 Notifier
The query above will result in the following System.NotSupportedException:
Unable to create a null constant value of type 'Models.Notifier'. Only entity types, enumeration types or primitive types are supported
in this context.
At the moment the only workaround I found is to loop the query result afterwards and manually populate Notifierlike this:
foreach (var c in caseList.Where(x => x.NotifierId.HasValue)
{
c.Notifier = (from x in context.Notifiers
where x.CaseId == c.CaseId
select new Notifier {
Name = x.Name
}).FirstOrDefault();
}
But I really don't want to do this because in my actual scenario it would generate hundreds of additional queries.
Is there any possible solution for a situation like this?.
I think you need to do that in two steps. First you can fetch only the data what you need with an anonymous type in a single query:
var caseList = (from x in context.Cases
where allowedCaseIds.Contains(x => x.CaseId)
select new {
CaseId = x.CaseId,
NotifierId = x.NotifierId,
NotifierName = x.Notifier.Name
}).ToList();
After that, you can work in memory:
List<Case> cases = new List<Case>();
foreach (var c in caseList)
{
var case = new Case();
case.CaseId = c.CaseId;
case.NotifierId = c.NotifierId;
case.NotifierName = c.NotifierId.HasValue ? c.NotifierName : null;
cases.Add(case);
}
You could try writing your query as a chain of function calls rather than a query expression, then put an .AsEnumerable() in between:
var caseList = context.Clases
.Where(x => allowedCaseIds.Contains(x.CaseId))
.AsEnumerable() // Switch context
.Select(x => new Case() {
CaseId = x.CaseId,
NotifierId = x.NotifierId,
Notifier = x.NotifierId.HasValue
? new Notifier() { Name = x.Notifier.Name }
: null
})
.ToList();
This will cause EF to generate an SQL query only up to the point where you put the .AsEnumerable(), further down the road, LINQ to Objects will do all the work. This has the advantage that you can use code that cannot be translated to SQL and should not require a lot of changes to your existing code base (unless you're using a lot of let expressions...)

Mongodb c# Conditions in query LINQ

My query look like this:
var query = from p in collection
where p.MinStockQuantity >= p.StockQuantity
select p;
I can't run because I have exception: Unsupported filter: ([MinStockQuantity] >= [StockQuantity])
This query also does not work, the same bug.
var collection = database.GetCollection<Product>("Product");
var builder = Builders<Product>.Filter;
var filter = builder.Where(o => o.MinStockQuantity > o.StockQuantity);
var query = collection.Find(filter).ToListAsync().Result;
How can I compare 2 fields ?
I know I am quite late but this works perfectly,try this :
var collection = database.GetCollection<Product>("Product");
var builder = Builders<Product>.Filter;
var filter = builder.Gt(o => o.MinStockQuantity , o.StockQuantity);
var query = collection.Find(filter).ToListAsync().Result;
Gt here is greater than.
there are various other methods like Gte =greater than equal to etc .
So my previous answer was obviously wrong. You shoud use that query
db.products.find({ $where: "this.MinStockQuantity > this.StockQuantity" })
To run this query in c# world you need to use BSON documents:
var doc = MongoDB.Bson.Serialization.BsonSerializer
.Deserialize<BsonDocument>
("{$where: \"this.MinStockQuantity > this.StockQuantity\"}");
var result = collection.Find(new CommandDocument(doc));
I used that query in my test application and it yells proper results.

Mongo update array element (.NET driver 2.0)

EDIT:
Not looking for the javascript way of doing this. I am looking for the MongoDB C# 2.0 driver way of doing this (I know it might not be possible; but I hope somebody knows a solution).
I am trying to update the value of an item embedded in an array on the primary document in my mongodb.
I am looking for a strongly typed way to do this. I am using the Mongodb c# 2.0 driver
I can do it by popping the element, updating the value, then reinserting. This just doesn't feel right; since I am overwriting what might have been written in the meantime.
Here is what I have tried so far but with no luck:
private readonly IMongoCollection<TempAgenda> _collection;
void Main()
{
var collectionName = "Agenda";
var client = new MongoClient("mongodb://localhost:27017");
var db = client.GetDatabase("Test");
_collection = db.GetCollection<TempAgenda>(collectionName);
UpdateItemTitle(1, 1, "hello");
}
public void UpdateItemTitle(string agendaId, string itemId, string title){
var filter = Builders<TempAgenda>.Filter.Eq(x => x.AgendaId, agendaId);
var update = Builders<TempAgenda>.Update.Set(x => x.Items.Single(p => p.Id.Equals(itemId)).Title, title);
var result = _collection.UpdateOneAsync(filter, update).Result;
}
Took me a while to figure this out as it doesn't appear to be mentioned in any of the official documentation (or anywhere else). I did however find this on their issue tracker, which explains how to use the positional operator $ with the C# 2.0 driver.
This should do what you want:
public void UpdateItemTitle(string agendaId, string itemId, string title){
var filter = Builders<TempAgenda>.Filter.Where(x => x.AgendaId == agendaId && x.Items.Any(i => i.Id == itemId));
var update = Builders<TempAgenda>.Update.Set(x => x.Items[-1].Title, title);
var result = _collection.UpdateOneAsync(filter, update).Result;
}
Notice that your Item.Single() clause has been changed to Item.Any() and moved to the filter definition.
[-1] or .ElementAt(-1) is apparently treated specially (actually everything < 0) and will be replaced with the positional operator $.
The above will be translated to this query:
db.Agenda.update({ AgendaId: 1, Items.Id: 1 }, { $set: { Items.$.Title: "hello" } })
Thanks, this was helpful. I have an addition though, I've used the above for arrays, pushing to a nested array and pulling from one. The issue I have found is that if I had an int array (So not an object, just a simple int array) that the PullFilter didn't actually work - "Unable to determine the serialization information" which is strange as it's only an array of ints. What I ended up doing was making it an array of objects with only one int parameter, and it all started to work. Possibly a bug, or perhaps my lack of understanding. Anyway, as I've struggled to find information about pulling and pushing to nested object arrays with the C# 2.0 driver, I thought I should post my findings here, as they use the above syntax.
var filter = Builders<MessageDto>.Filter.Where(x => x._id == entity.ParentID && x.NestedArray.Any(i => i._id == entity._id));
var update = Builders<MessageDto>.Update.PullFilter(x => x.NestedArray.ElementAt(-1).User, Builders<User>.Filter.Eq(f => f.UserID, userID));
Collection<MessageDto>(currentUser).UpdateOneAsync(filter, update);
And also:
var filter = Builders<MessageDto>.Filter.Where(x => x._id == entity.ParentID && x.NestedArray.Any(i => i._id == entity._id));
var update = Builders<MessageDto>.Update.Push(x => x.NestedArray.ElementAt(-1).Users, new User { UserID = userID });
Collection<MessageDto>(currentUser).UpdateOneAsync(filter, update);
The correct way to update a Document or sub array is as follows:
var filter = Builders<Declaracion>.Filter.Where(x => x.Id == di && x.RemuneracionMensualActual.RemuneracionActIndustrial.Any(s => s.Id == oid));
var update = Builders<Declaracion>.Update.Set(x => x.RemuneracionMensualActual.RemuneracionActIndustrial.ElementAt(-1).Ingreso, datos.ActividadIndustrial.Ingreso)
.Set(x => x.RemuneracionMensualActual.RemuneracionActIndustrial.ElementAt(-1).RazonSocial, datos.ActividadIndustrial.RazonSocial)
.Set(x => x.RemuneracionMensualActual.RemuneracionActIndustrial.ElementAt(-1).TipoNegocio, datos.ActividadIndustrial.TipoNegocio);

BaseQuery class missing in new NEST dll version NEST.1.1.2

What is the replacement of BaseQuery class in new version.
I couldn't find it anywhere.
My problem is how to generate syntax in c# for the search criteria as:
public class TextSearch
{
public string Headline {get;set;}
public string Summary {get;set;}
}
I need to search using text 'you', against two column as OR operator, Column 1 summary and Column 2 headline.
Earlier I was doing,
var orQuery = new List<BaseQuery>();
if (!string.IsNullOrEmpty(searchtext))
{
orQuery .Add(Query<TextSearch>.Terms("headline", searchOptions.text.ToLower().Split(' ')));
orQuery .Add(Query<TextSearch>.Terms("summary", searchOptions.text.ToLower().Split(' ')));
}
var finalQuery = new List<BaseQuery>();
finalQuery .Add(Query<TextSearch>.Bool(o => o.Should(orQuery.ToArray())));
Now this doesn't work.
Is there any better syntax for searching in new version.
The search criteria should using LIKE with OR,
e.g. summary LIKE '%you%' OR headling LIKE '%you%'
The documentation on the breaking changes in NEST 1.0 is pretty complete:
http://nest.azurewebsites.net/breaking-changes.html
We renamed BaseQuery to QueryContainer
The query can be:
client.Search<TextSearch>(s=>s
.Query(q=>
q.Terms("headline", words)
|| q.Terms("summary", words)
)
)
If words is empty or null that part is not rendered see the conditionless query section here:
http://nest.azurewebsites.net/nest/writing-queries.html
#Martijn Laarman
Since we have numerous filter criteria that will be dynamic based on user selected filter,
I'm constructing Filter Query in amethod based on user slelcted filter and then pass it to Search<> method as:
QueryContainer mainQuery = null;
if (!string.IsNullOrEmpty(searchOptions.SearchText))
{
var headline = Query<T>.Terms("headline", searchOptions.Headline.ToLower());
var summary = Query<T>.Terms("fullSummary", searchOptions.Summary.ToLower());
mainQuery &= (headline || summary);
}
if (searchOptions.FromDate != DateTime.MinValue && searchOptions.ToDate != DateTime.MinValue)
{
var dateFilter = Query<T>.Range(
r => r.OnField("processedDate").GreaterOrEquals(searchOptions.FromDate, ElasticDateFormat).LowerOrEquals(searchOptions.ToDate, ElasticDateFormat));
mainQuery &= dateFilter;
}
var result = Client.Search<T>(s => s.Query(mainQuery ).Size(Int32.MaxValue));
Here Client is a property that returns ElasticClient object.
Hope thats the correct way of doing.

Is it possible to use a string for a LINQ query expression?

I need to extract some records if some variables have some values.
For example, if status>0 I need to filter result like :
where object.id=status
else, if status=0, I need to remove this where clauses and return all elements. I'll get rid about :
if(status>0)
do a linq query with the where clauses
else
do a link query with that where clauses
too much code, because the variables to check could be more than 4-5.
Is it possible to "inject" a sort of string on LINQ? (So i can create my string and pass it to the LINQ).
I mean somethings like :
string myQuery="";
if(status>0)
myQuery="where object.id=status";
else
myQuery="";
is it possible? (Classic mysql behaviour).
Since LINQ is lazy, you can just do
var query = ...
if (status > 0)
{
query = query.Where(o => o.id == status);
}
You can build up a query like this:
IEnumerable<MyEntity> results = MyEntityContext.MyEntities;
if (status > 0)
results = results.Where(e => e.id == status);
Does that help?
It is possible using Dynamic LINQ, see ScottGu's blog post:
Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)
You could do it like this:
var query = database.MyTable.Where(/* where for all records */);
if (status > 0) {
query = query.Where(o => o.id == status);
}
Linq (to sql and EF) is smart enough to merge the where conditions and send one SQL statement to the database.
It is possible using dynamic linq - see How to create LINQ Query from string?
My answer there links to Scott Gu's posts and the sample code from Microsoft - http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
AFAIK, you will need to have 2 different queries.
if(status > 0)
{
var myquery = From ....
where object.id = status
}
else
{
var myquery = From ..
}
Another option: query.Where(x=>(status>0? x.id==status : 1==1))
Are you attempting to do a conditional LINQ query? if so maybe this would help
var nums = new List<int>() { 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4 };
bool getOdd = true;
var query = nums.AsQueryable();
if (getOdd) {
query = query.Where(i => i == 1 || i == 3);
} else {
query = query.Where(i => i == 2 || i == 4);
}
var result = query.ToList();
You can write the following
IQueryable<T> query = dbContext.SomeObjectSet;
if (condition1) {
query = query.Where(...)
}
if (condition2) {
query = query.Where(...)
}
However, you you want to query all entities, you can filter in memory afterwards using LINQ to SQL

Categories