Why is $sample missing in MongoDB .NET Driver()? - c#

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

Related

MongoDB LINQ query - Update records amended for User object - immutable _id error

I'm trying to use MongoDB LINQ to update my user object's records that are updated, but I'm receiving the error:
"A Performing an update would modify the immutable field '_id'."
I create a user object as follows:
var updateUser = new User
{
firstname = userProfileFirstname,
surname = userProfileSurname,
dob = dateOfBirth,
userGender = userGender.ToString(),
userCountry = userCountry.ToString(),
};
await MongoService.UpdateUser(updateUser, userId); //this is my own unique foreign Id - not an object ID
Then in my Mongo Services, I have the following Replace One Async query:
public async static Task UpdateUser(User item, String fUserID)
{
var filter = Builders<User>.Filter.Eq("foreignId", fUserID);
await UsersCollection.ReplaceOneAsync(filter, item, new UpdateOptions { IsUpsert = true });
}
Please could help with how I can fix this?

Mongodb C# driver throwing Element Name '_t' is not valid on UpdateOneAsync

I am getting MongoDb.Bson.SerializationException: 'Element name '_t' is not valid.
I read all posts online that appear to be similar issue at first, however, in those posts, element name is specified, here, i am only getting '_t' even by trying different class objects.
var database = AppConfig.client.GetDatabase("test");
var collection = database.GetCollection<BsonDocument>("testcollection");
var filter = Builders<Student>.Filter.Eq(g => g.Name, "oldName");
var update = Builders<Student>.Update.Set(g => g.Name, "NewName");
var updateResult = await collection.UpdateOneAsync(filter.ToBsonDocument(), update.ToBsonDocument());
Also, for all examples i have seen online for UpdateOneAsync function, filter and update below do NOT need to be BSON documents, however, my code won't compile unless I do .ToBSONDocument() as above.
var updateResult = await collection.UpdateOneAsync(filter, update);
My class is minimal:
public class Student
{
[BsonElement("Name")]
public string Name { get; set; }
[BsonElement("Age")]
public int Age { get; set; }
}
Can someone please help figure out what is wrong with above?
Update: How to use render for Update.Set
var registry = BsonSerializer.SerializerRegistry;
var serializer = registry.GetSerializer<Student>();
var filter = Builders<Student>.Filter.Eq(g=> g.Name, "NewName").Render(serializer, registry);
//I think update syntax is not correct.
var update = Builders<Student>.Update.Set(g => g.Name, "Changed").Render(serializer, registry);
//update is throwing error:cannot convert from Mongodb.bson.bsonvalue to mongodb.Driver.updatedefinition<MongoDB.Bson.BsonDocument
var updateResult = await collection.UpdateOneAsync(filter, update);
it's impossible to use ToBsonDocument as you did. The easiest fix is using not typed builders:
var filter = Builders<BsonDocument>.Filter.Eq("name", "oldName");
If you want to use typed builder, you should call Render as below:
var registry = BsonSerializer.SerializerRegistry;
var serializer = registry.GetSerializer<Student>();
var filter = Builders<Student>.Filter.Eq(e=>e.Name, "oldName").Render(serializer, registry);

Retrieve "number" Luis entity in bot framework and display it right away

I am trying to recognize the entity "number" associated to the Intent command.
Here my entity in Luis
I use the Core Bot example and i let the classes names stay the same. https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/csharp_dotnetcore/13.core-bot
I added the "number" entity here in flightbooking.cs
// Built-in entities
public DateTimeSpec[] datetime;
public double[] number;
I added this in flightbookingex.cs
public string Order_Number
=> Entities.number.ToString();
I created a class here : Order_Details.cs
namespace Microsoft.BotBuilderSamples
{
public class Order_Details
{
public string Order_Number { get; set; }
}
}
And when i want to retrieve the result in maindialog.cs
case FlightBooking.Intent.commande:
var commandemessagetext = "Voici le bon de commande";
var orderDetails = new Order_Details()
{
// Get destination and origin from the composite entities arrays.
Order_Number = luisResult.Order_Number,
};
var travelDateMsg = { result.Order_Number };
It says Cannot initialize an implicitly-typed variable with an array initializer and The name 'result' does not exist in the current context
I did not find another way to do this. I would like to display the result of travelDateMsg in the "case FlightBooking.Intent.commande". In the core bot example, it is displayed in another stepcontext.
i also tried the following piece of code, but it somehow does not work properly.
case FlightBooking.Intent.commande:
var commandemessagetext = "Here the order";
var order_count= luisResult.Entities;
var messageTexto = $"you have ordered {order_count}";
var message = MessageFactory.Text(messageTexto, messageTexto, InputHints.IgnoringInput);
await stepContext.Context.SendActivityAsync(message, cancellationToken);
the result is "you have ordered Microsoft.BotBuilderSamples.FlightBooking+_Entities"
Is there a simple way to return the value of an ententy in the same block of code than the one who detect the intent?
Thank you very much in advance for any suggestion on this
To answer the second error The name 'result' does not exist in the current context is because you have a typo:
case FlightBooking.Intent.commande:
var commandemessagetext = "Voici le bon de commande";
var orderDetails = new Order_Details()
{
// Get destination and origin from the composite entities arrays.
Order_Number = luisResult.Order_Number,
};
var travelDateMsg = { result.Order_Number };
There isn't a result there, there's a luisResult.

How to avoid adding a reference field in very small entries (thus avoiding doubling collection size)?

I have a User class that accumulates lots of DataTime entries in some List<DateTime> Entries field.
Occasionally, I need to get last 12 Entries (or less, if not reached to 12). It can get to very large numbers.
I can add new Entry object to dedicated collection, but then I have to add ObjectId User field to refer the related user.
It seems like a big overhead, for each entry that holds only a DateTime, to add another field of ObjectId. It may double the collection size.
As I occasionally need to quickly get only last 12 entries of 100,000 for instance, I cannot place these entries in a per-user collection like:
class PerUserEntries {
public ObjectId TheUser;
public List<DateTime> Entries;
}
Because it's not possible to fetch only N entries from an embedded array in a mongo query, AFAIK (if I'm wrong, it would be very gladdening!).
So am I doomed to double my collection size or is there a way around it?
Update, according to #profesor79's answer:
If your answer works, that will be perfect! but unfortunately it fails...
Since I needed to filter on the user entity as well, here is what I did:
With this data:
class EndUserRecordEx {
public ObjectId Id { get; set; }
public string UserName;
public List<EncounterData> Encounters
}
I am trying this:
var query = EuBatch.Find(u => u.UserName == endUser.UserName)
.Project<BsonDocument>(
Builders<EndUserRecordEx>.Projection.Slice(
u => u.Encounters, 0, 12));
var queryString = query.ToString();
var requests = await query.ToListAsync(); // MongoCommandException
This is the query I get in queryString:
find({ "UserName" : "qXyF2uxkcESCTk0zD93Sc+U5fdvUMPow" }, { "Encounters" : { "$slice" : [0, 15] } })
Here is the error (the MongoCommandException.Result):
{
{
"_t" : "OKMongoResponse",
"ok" : 0,
"code" : 9,
"errmsg" : "Syntax error, incorrect syntax near '17'.",
"$err" : "Syntax error, incorrect syntax near '17'."
}
}
Update: problem identified...
Recently, Microsoft announced their DocumentDB protocol support for MongoDB. Apparently, it doesn't support yet all projection operators. I tried it with mLab.com, and it works.
You can use PerUserEntries as this is a valuable document structure.
To get part of that array we need to add projection to query, so we can get only x elements and this is done server side.
Please see snippet below:
static void Main(string[] args)
{
// To directly connect to a single MongoDB server
// or use a connection string
var client = new MongoClient("mongodb://localhost:27017");
var database = client.GetDatabase("test");
var collection = database.GetCollection<PerUserEntries>("tar");
var newData = new PerUserEntries();
newData.Entries = new List<DateTime>();
for (var i = 0; i < 1000; i++)
{
newData.Entries.Add(DateTime.Now.AddSeconds(i));
}
collection.InsertOne(newData);
var list =
collection.Find(new BsonDocument())
.Project<BsonDocument>
(Builders<PerUserEntries>.Projection.Slice(x => x.Entries, 0, 3))
.ToList();
Console.ReadLine();
}
public class PerUserEntries
{
public List<DateTime> Entries;
public ObjectId TheUser;
public ObjectId Id { get; set; }
}

How to remove one 'document' by 'ID' using the Official C# Driver for MongoDB?

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

Categories