I have many complex queries that I sometimes wish to check directly against Mongo for debugging \ explaining() purposes.
With the newer 2.0+ c# driver, i'm not sure how to do this. With the previous version there was a thing called IMongoQuery and This worked.
A simple example:
FilterDefinition<LalalaEvent> filter = Builders<LalalaEvent>.Filter
.Where(e => ids.Contains(e.Id) && e.Deleted != true );
I was trying to solve the same problem today. Here is what I found.
public static class MongoExtensions
{
public static BsonDocument RenderToBsonDocument<T>(this FilterDefinition<T> filter)
{
var serializerRegistry = BsonSerializer.SerializerRegistry;
var documentSerializer = serializerRegistry.GetSerializer<T>();
return filter.Render(documentSerializer, serializerRegistry);
}
}
I didn't have access to a collection when I was calling it, so I couldn't use the above solutions.
This allows you to do
var json = filter.RenderToBsonDocument().ToJson();
If you're using the latest version of the driver, which is 2.0.1 you can easily put that filter in a Find operation, get back an IFindFluent and print its ToString:
var filter = Builders<LalalaEvent>.Filter.Where(e => ids.Contains(e.Id) && e.Deleted != true);
var findFluent = collection.Find(filter);
Console.WriteLine(findFluent);
For example for me this prints:
find({ "_id" : { "$in" : [1, 2, 3] }, "Deleted" : { "$ne" : true } })
You are able to perform that using the collection's properties:
var result = filter.Render(collection.DocumentSerializer,
collection.Settings.SerializerRegistry).ToString();
Related
I'm using MongoDB.Driver 2.11.6 version, I installed it with NuGet package manager in visual studio 2019. I wrote this method:
public async Task<IActionResult> ExploreUsers(ObjectId id)
{
ObjectId currentUser = id;
var db = _mongoService.GetDb;
var userCollection = db.GetCollection<User>("Users");
// Get CurrentUser:
var filterContacts = Builders<User>.Filter.Eq(u => u.Id, currentUser);
User currentDocument = (await userCollection.FindAsync<User>(filterContacts))
.First<User>();
// Get Id of Users in Contacts:
var contactsArray = currentDocument.Contacts.Select(x => x.OtherUserId);
// Get Users:
var filterPom = Builders<User>.Filter.Nin("_id", contactsArray);
var filter = Builders<User>.Filter.And(!filterContacts & filterPom);
var users = await userCollection.Aggregate<User>().Match(filter)
.Limit(20)
.ToListAsync();
//.ToString();
return Ok(users.Select(x => new UserDTO { ObjectId = x.Id.ToString(),
Username = x.Username }));
}
In this method I first find User(I will call him currentUser) with Id from argument of function, User have property Contacts of type List<Contact> and information that is important in this method from Contact is OtherUserId and this is Id of User in same collection and contactsArray is of type List that stores information. Last query returns 20 or less Users from collection that is not in Contacts of currentUser.
But I want this query:
db.getCollection("Users")
.aggregate([
{ "$match" : { "_id" :
{ "$nin" : [ObjectId("60286f9f5e7da329ba5eb813"),
ObjectId("60286f9f5e7da329ba5eb813"),
ObjectId("602a5b642e156713e69d0a20")]
}
}
},
{ "$sample" : {size: 1} }
])
Only difference is that Sample() doesn't exist and in documentation. Is there way to get query I want using my version of MongoDB.Driver(2.11.6)?
Also before this query I first call FindAsync<User> and then perform simple LINQ query to get IEnumerable<ObjectId> that I use in last query. Is there way to let DBMS do this for me? If there is a way how to modify my current query (it is not important for me is it code in C# or mongo shell query)?
Edit
I found answer on first question on link from comment: Random elements. So instead of Limit(20) I added:
AppendStage<User>($#"{{ $sample: {{ size: {sampleSize} }} }}")
where sampleSize is int.
Documentation:
Driver Documentation(example of usage in last example Text search)
API Documentation
I'm trying to port over JavaScript code that interacts with a MongoDb to C# .NET and having problems with a delete/pull operation. I can't seem to figure out how to iterate over the Ponies array and remove a single ObjectId by matching to a passed in ObjectId.
{
"_id" : ObjectId("388e8a66fe2af4e35c60e"),
"Location" : "rainbowland",
"Ponies" : [
ObjectId("388e8a66fe2af4e35c24e83c"),
ObjectId("388e8a66fe2af4e35c24e860"),
ObjectId("388e8a66fe2af4e35c24e83d")
]
}
I've tried several different ways based on what I've found online but nothing seems to work. When I check the collection to see if 388e8a66fe2af4e35c24e860 was removed from the Ponies array I see that it wasn't removed. Everytime I execute the code and look at the ModifiedCount for resultPonies it shows zero. Two examples of what I've tried doing are below.
var pony = new Pony() { Id = new ObjectId("388e8a66fe2af4e35c24e860") }
var pullPonies = Builders<Ranch>.Update.PullFilter(x => x.Ponies,
y => y.Id == pony.Id);
var filterPonies = Builders<Ranch>.Filter.Where(x => true);
var resultPonies = _context.Ranches.UpdateMany(filterPonies, pullPonies);
OR
var pullPonies = Builders<BsonDocument>.Update.PullFilter("Ponies",
Builders<ObjectId>.Filter.Eq("ObjectId", pony.Id));
var filterPonies = Builders<BsonDocument>.Filter.Where(x => true);
var resultPonies = _context.Ranches.UpdateMany(filterPonies, pullPonies);
Here is the JavaScript code that I believe works but haven't been able to test yet.
db.collection("Ranches").update({}, {$pull: {Ponies: pony._id}}, {multi: true});
Ok, I found a solution for the problem. Since I'm not actually going to be filtering within the array of objects, they are all ObjectIds in a BsonArray, I need to find where the value in the array matches the given ObjectId. In those cases the array item should be pulled out. If the object in the array had other properties then I would probably want to use the PullFilter. But in this case I don't need to filter the additional level. That is at least what I believe I was doing wrong. Here are the solutions
var ponyId = new ObjectId("388e8a66fe2af4e35c24e860");
var pullPonies = Builders<Ranch>.Update.Pull(x => x.Ponies, ponyId);
// This line is to retrieve all Ranch objects in the collection
var filterPonies = Builders<Ranch>.Filter.Where(x => true);
var resultPonies = _context.Ranches.UpdateMany(filterPonies, pullPonies);
Or
var ponyId = new ObjectId("388e8a66fe2af4e35c24e860");
var pullPonies = Builders<BsonDocument>.Update.Pull("Ponies", ponyId));
// This line is to retrieve all Ranch objects in the collection
var filterPonies = Builders<BsonDocument>.Filter.Where(x => true);
var resultPonies = _context.Ranches.UpdateMany(filterPonies, pullPonies);
I've run into what I can only understand to be a bug in the C# driver.
This gist illustrates the problem.
If I run
collection.UpdateOneAsync(
"{ \"_id\" : ObjectId(\"5656277cd4d37b13b4e7e009\"), \"Addresses.Index\" : 4 },
"{ \"$set\" : { \"Addresses.$\" : { \"_t\" : [\"Address\", \"EmailAddress\"], \"Index\" : 4, \"MailTo\" : \"Never#home.com\" } } }")
I get the desired result.
If however I use the Builders to build the filter definition and update definition like so:
var filter = Builders<Person>
.Filter
.And(Builders<Person>.Filter.Eq(p => p.Id, person.Id),
Builders<Person>.Filter.Eq("Addresses.Index", 4));
var update = Builders<Person>.Update.Set("Addresses.$", new EmailAddress { Index = 4, MailTo = "Never#home.com" });
then I must change my call to update to be
await collection.OfType<Person>().UpdateOneAsync(filter, update);
and the call to OfType results in the wrong address being replaced.
This is unrelated to the MongoDB C# driver, but it seems to be a bug in MongoDB itself when the type is used in the query.
You can see that this causes the same issue without using OfType, but specifying the type field explicitly (i.e. "_t"):
var filter = Builders<Person>.Filter.And(
new BsonDocument("_t", "Person"),
Builders<Person>.Filter.Eq(p => p.Id, person.Id),
Builders<Person>.Filter.Eq("Addresses.Index", 4));
var update = Builders<Person>.Update.Set(
"Addresses.$",
new EmailAddress { Index = 4, MailTo = "Never#home.com" });
await db.GetCollection<Person>("Objects").UpdateOneAsync(filter, update);
You can see the query this will generate with this piece of code:
Console.WriteLine(db.GetCollection<Person>("Objects").Find(filter));
And the query is the following which is perfectly correct:
{ "_t" : "Person", "_id" : ObjectId("5656356f64c22e5d38aeb92e"), "Addresses.4 }
I have many complex queries that I sometimes wish to check directly against Mongo for debugging \ explaining() purposes.
With the newer 2.0+ c# driver, i'm not sure how to do this. With the previous version there was a thing called IMongoQuery and This worked.
A simple example:
FilterDefinition<LalalaEvent> filter = Builders<LalalaEvent>.Filter
.Where(e => ids.Contains(e.Id) && e.Deleted != true );
I was trying to solve the same problem today. Here is what I found.
public static class MongoExtensions
{
public static BsonDocument RenderToBsonDocument<T>(this FilterDefinition<T> filter)
{
var serializerRegistry = BsonSerializer.SerializerRegistry;
var documentSerializer = serializerRegistry.GetSerializer<T>();
return filter.Render(documentSerializer, serializerRegistry);
}
}
I didn't have access to a collection when I was calling it, so I couldn't use the above solutions.
This allows you to do
var json = filter.RenderToBsonDocument().ToJson();
If you're using the latest version of the driver, which is 2.0.1 you can easily put that filter in a Find operation, get back an IFindFluent and print its ToString:
var filter = Builders<LalalaEvent>.Filter.Where(e => ids.Contains(e.Id) && e.Deleted != true);
var findFluent = collection.Find(filter);
Console.WriteLine(findFluent);
For example for me this prints:
find({ "_id" : { "$in" : [1, 2, 3] }, "Deleted" : { "$ne" : true } })
You are able to perform that using the collection's properties:
var result = filter.Render(collection.DocumentSerializer,
collection.Settings.SerializerRegistry).ToString();
Can someone please show me, if there is a better way to remove one document from MongoDB using the Official C# Driver than what I have below-
var query = Query.EQ("_id", a.Id);
database.GetCollection<Animal>("Animal").Remove(query);
This code works, but seems too much work to me. The "Save" command for example- takes an instance and updates it. I want something like- Remove(item).
Remarks: I'm trying to use the official driver of C# rather than NoRM or Samus which seems out of date.
That's the way you do it. I'm sure you know this, but if you want to put it on one line you could combine it so you don't need to define a query variable:
collection.Remove(Query.EQ("_id", a.Id));
If the [id] is string, you must use ObjectId instance explicitly.
var query = Query.EQ("_id", ObjectId.Parse(id));
The Simplest Way
Remove a document from a collection for C# MongoDB Driver (v2.0 or later)-
collection.DeleteOne(a => a.Id==id);
Or-
await collection.DeleteOneAsync(a => a.Id==id);
My ASP.NET Core MVC controller's action accepts Id as a string parameter. Then I parse it and use the result in the DeleteOne() statement:
[HttpPost]
public IActionResult Delete(string id)
{
ObjectId objectId = ObjectId.Parse(id);
DbContext.Users.DeleteOne(x => x.Id == objectId);
return null;
}
var filter = Builders<BsonDocument>.Filter.Eq("_id",ObjectId.Parse(id));
var x = data.DeleteOne(filter);
I am using this with the current version ( in 2019 ), and it works.
If you only want to delete a docuement from a collection :
public async Task<bool> Delete(string id)
{
FilterDefinition<YourModel> filter = Builders<YourModel>.Filter.Eq(p => p.Id, id);
var deleteResult = await _context.YourModelCollection.DeleteOneAsync(filter);
return deleteResult.IsAcknowledged && deleteResult.DeletedCount > 0;
}
And if you want to delete all the docuements :
public async Task<bool> DeleteAll()
{
FilterDefinition<YourModel> filter = Builders<YourModel>.Filter.Empty;
var deleteResult = await _context.YourModelCollection.DeleteManyAsync(filter);
return deleteResult.IsAcknowledged && deleteResult.DeletedCount > 0;
}