Getting started with MongoDB on C# - query does not work - c#

I am starting with MongoDb on c#. At the end, I need function which simply checks if the user exists in DB -thats it.
I am a complete beginner so naturally example right from MongoDb tutorial does not work
here are examples:
public static async Task<List<User>> QueryDB(User u)
{
var collection = _database.GetCollection<User>("UserData");
var filter = Builders<User>.Filter.Eq("id", u.id);
var result = await collection.Find(filter).ToListAsync();
return result;
}
or
public static async Task<long> QueryDB(User u)
{
var collection = _database.GetCollection<User>("UserData");
var filter = Builders<User>.Filter.Eq("id", u.id);
var result = await collection.Find(filter).CountAsync();
return result;
}
What is wrong with these functions? Or how should I call them? Because now it throws a timeout.
Can this be done without async/await? I think I don't need it

I would suggest using FindAsync, it works better and you prevent deadlocks in c# and let mongo do its job.
public async Task<List<User>> QueryDB(User u)
{
var collection = _database.GetCollection<User>("UserData");
var filter = Builders<User>.Filter.Eq(us => us.id, u.id); //best practice to prevent errors on field name such as extra letter or capital vs lowercase.
List<User> fetchedUsers = new List<User>()
using (var cursor = await collection.FindAsync(filter))
{
while (await cursor.MoveNextAsync())
{
var batch = cursor.Current;
foreach (User user in batch)
fetchedUsers.Add(user);
}
}
return fetchedUsers;
}
if you want to "advance" you can use this method to all classes that have id field
public async Task<List<T>> QueryDB(this IMongoCollection<T> collection, T entity) where T : Idable
{
var filter = Builders<T>.Filter.Eq(ts => ts.id, entity.id);
List<T> fetchedData = new List<T>()
using (var cursor = await collection.FindAsync(filter))
{
while (await cursor.MoveNextAsync())
{
var batch = cursor.Current;
foreach (T res in batch)
fetchedData.Add(res);
}
}
return fetchedData;
}
interface Idable
{
string id {get;};
}
public class User : Idable
{
...
}
you simply have this method "attached" to all your collections now.
just create a list, lets say type of User, and call the collection.QueryDB(User u) and you will get a list of all users with the same id, yes..probably only 1, but you can modify this method and just play with it.

Related

Controller Check if Agent Name is already created

I would like to add logic to check if the agent name is already created and alert the user. I created this IQueryable SearchAgents which takes in a string query and I was going to add it to the controller but I am not sure if this is the correct way.
Is this the correct path for validating an agent is already in the system?
AgentController
[HttpPost]
[ApplicationApiAuthorize("Administrator, ContentManager")]
public IHttpActionResult CreateAgent([FromBody]AgentModel agentModel)
{
LogHelper.Info($"Creating agent {agentModel.Name}");
//Search if Agent name is in the system
var AgentId = AgentsDataService.SearchAgents.Select(a => new AgentModel {Name = agentModel.Name }).ToList();
var agentEntity = new Agent();
Mapper.DynamicMap(agentModel, agentEntity);
var agentInformationEntities = Mapper.Map<IEnumerable<AgentInformation>>(agentModel.AgentInformations);
agentEntity.AgentInformations = new EntitySet<AgentInformation>();
agentEntity.AgentInformations.AddRange(agentInformationEntities);
var operationResult = AgentsDataService.InsertAgent(agentEntity);
var result = Ok(new
{
Value = Mapper.Map<AgentModel>(operationResult)
});
return result;
}
AgentDataService
public IQueryable<Agent> SearchAgents(string query)
{
return GetAllAgents().Where(a => a.Name.Contains(query)).OrderBy(a => a.Name);
}
Personally since you only want to know if the entity exists in the database or not I would have the following method in the AgentDataService
public bool AgentExists(string agentName)
{
return GetAllAgents().Any(x => x.Name.Equals(agentName, StringComparison.InvariantCultureIgnoreCase));
}
or the async version (Assuming you are using Entity Framework):
public async Task<bool> AgentExistsAsync(string agentName)
{
return await GetAllAgents().AnyAsync(x => x.Name.Equals(agentName, StringComparison.InvariantCultureIgnoreCase));
}
Then you can call this from your controller and inform the user if the agent exists.

How to fix DeleteManyAsync returning 0 records deleted with Filter?

I have a delete method that takes in an IEnumerable of Ids that are of type string, and have a filter taking in those ids using Filter.In. However when passing in a set of ids I am getting a count of 0 for records deleted. Is my filter causing the issue?
I've created a test method to test my delete method and am passing in ids to try and have them deleted.
Test Solution
MongodDB Test method for delete method
[Theory]
[InlineData(1)]
[InlineData(100)]
public async void TEST_DELETE(int quantity)
{
using (var server = StartServer())
{
// Arrange
var collection = SetupCollection(server.Database, quantity);
var dataUtility = new MongoDataUtility(server.Database,
MongoDbSettings);
var service = new MongoDatabaseService(dataUtility, Logger);
var items =
collection.FindSync(FilterDefinition<BsonDocument>.Empty)
.ToIdCollection();
_output.WriteLine(JsonConvert.SerializeObject(items,
Formatting.Indented));
// Act
var result = await
dataUtility.DeleteIdentifiedDataAsync(items, CollectionName);
_output.WriteLine(JsonConvert.SerializeObject(result,
Formatting.Indented));
// Assert
Assert.True(result.DeletedCount.Equals(items.Count));
}
}
Setup collection
public IMongoCollection<BsonDocument> SetupCollection(IMongoDatabase db,
int quantity)
{
var collection = db.GetCollection<BsonDocument>(CollectionName);
AddCreateDateIndex(collection);
SeedData(collection, quantity);
return collection;
}
Seed data
public void SeedData(IMongoCollection<BsonDocument> collection, int?
quantity = null)
{
if (quantity != null && quantity > 0)
{
collection.InsertMany(GenerateTestData((int)quantity));
}
}
Project
MongoDB delete method
public async Task<DeleteResult>
DeleteIdentifiedDataAsync(IEnumerable<ObjectId> ids, string Resource,
CancellationToken cancellationToken = default)
{
var collection = _db.GetCollection<BsonDocument>(Resource);
var filter = Builders<BsonDocument>.Filter.In("_id", ids);
if (ids != null && ids.Any() )
{
return await collection.DeleteManyAsync(filter,
cancellationToken);
}
return null;
}
Extensions
public static ICollection<ObjectId> ToIdCollection(this
IAsyncCursor<BsonDocument> #this)
{
return #this.Find(Builders<BsonDocument>.Filter.Empty)
.ToEnumerable()
.Select(s => s["_id"].AsObjectId)
.ToList();
}
Your ToIdCollection method gets all the ids but also converts them from ObjectId to String when you run .Select(dict => dict["_id"].ToString()). MongoDB compares both values and types when you run DeleteManyAsync and that's why there is no match - you're trying to compare list of strings against ObjectIds that are stored in the database.
To fix that you can replace ToIdCollection with following implementation:
return #this.Find(Builders<BsonDocument>.Filter.Empty)
.ToEnumerable()
.Select(s => s["_id"].AsObjectId)
.ToList()

How to read boolean value from Cosmos DB from C#

I have some data in documentdb with following structure:
{
id:1,
key:"USA",
states:["New York","New Jersey", "Ohio", "Florida" ]
}
I need to check if "California" is available in the above document with a query from C# using CreateDocumentQuery which returns true/false. How can I read the boolean value from CreateDocumentQuery?
Assuming you have a DTO looking something like this:
class Item
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("states")]
public List<string> States { get; set; }
}
You can use the Contains method on an array to check if the value exists or not. The LINQ to SQL converter will turn this into a sql query for you. All you have to do is to add any other predicates in the where clause and run it. I am intentionally only selecting the Id of the document to make save some RUs and make it run faster.
If you add the partition key in the expression present in the where clause, or if you know what the partition key value is, please set it in the feed options to enhance performance. Cross partition queries are not really recommended as part of your day to day workflow.
You can use the CountAsync() extension method of the DocumentQueryable to get the count of doucments matching the predicate and then do a > 0 to see if it exists.
Here is the code for that:
public async Task<bool> ArrayContainsAsync()
{
var documentClient = new DocumentClient(new Uri("https://localhost:8081"), "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==");
var collectionUri = UriFactory.CreateDocumentCollectionUri("dbname", "collectionName");
return (await documentClient
.CreateDocumentQuery<Item>(collectionUri, new FeedOptions { EnableCrossPartitionQuery = true })
.Where(x => x.States.Contains("California")).CountAsync()) > 0;
}
However, the code below will take control of the query and exit on first match which will be way more efficient than the code above.
public async Task<bool> ArrayContainsAsync()
{
var documentClient = new DocumentClient(new Uri("https://localhost:8081"), "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==");
var collectionUri = UriFactory.CreateDocumentCollectionUri("dbname", "collectionName");
var query = documentClient
.CreateDocumentQuery<Item>(collectionUri, new FeedOptions { EnableCrossPartitionQuery = true })
.Where(x => x.States.Contains("California")).Select(x=> x.Id).AsDocumentQuery();
while (query.HasMoreResults)
{
var results = await query.ExecuteNextAsync();
if (results.Any())
return true;
}
return false;
}

Fluent Validation changing CustomAsync to MustAsync

Could some one please help me to resolved this? i'm trying to change CustomAsync to MustAsync, but i couldn't make things to work. Below is my custom method
RuleFor(o => o).MustAsync(o => {
return CheckIdNumberAlreadyExist(o)
});
private static async Task<ValidationFailure> CheckIdNumberAlreadyExist(SaveProxyCommand command)
{
if (command.Id > 0)
return null;
using (IDbConnection connection = new SqlConnection(ConnectionSettings.LicensingConnectionString))
{
var param = new DynamicParameters();
param.Add("#idnumber", command.IdNumber);
var vehicle = await connection.QueryFirstOrDefaultAsync<dynamic>("new_checkDuplicateProxyIdNumber", param, commandType: CommandType.StoredProcedure);
return vehicle != null
? new ValidationFailure("IdNumber", "Id Number Already Exist")
: null;
}
}
To make it work with the latest version of the FluentValidation, I had to use the codes like below.
RuleFor(ws => ws).MustAsync((x, cancellation) => UserHasAccess(x)).WithMessage("User doesn't have access to perform this action");
Please notice the lambda expression here MustAsync((x, cancellation) => UserHasAccess(x)), without this I was always getting an error as cannot convert from 'method group' to 'Func<Worksheet, CancellationToken, Task<bool>>
Below is my custom UserHasAccess function.
private async Task <bool> UserHasAccess(Worksheet worksheet) {
var permissionObject = await _dataProviderService.GetItemAsync(worksheet.FileItemId);
if (permissionObject is null) return false;
if (EditAccess(permissionObject.Permission)) return true;
return false;
}
I'm assuming you're using a version of FluentValidation prior to version 6, as you're not passing in a Continuation Token, so I've based my answer on version 5.6.2.
Your example code does not compile, for starters, as you're missing a semi-colon in your actual rule. You are also evaluating two different properties on the SaveProxyCommand parameter.
I've built a very small POC based on some assumptions:
Given 2 classes:
public class SaveProxyCommand {
public int Id { get; set; }
}
public class ValidationFailure {
public string PropertyName { get; }
public string Message { get; }
public ValidationFailure(string propertyName, string message){
Message = message;
PropertyName = propertyName;
}
}
And a validator:
public class SaveProxyCommandValidator : AbstractValidator<SaveProxyCommand>{
public SaveProxyCommandValidator()
{
RuleFor(o => o).MustAsync(CheckIdNumberAlreadyExists)
.WithName("Id")
.WithState(o => new ValidationFailure(nameof(o.IdNumber), "Id Number Already Exist"));
}
private static async Task<bool> CheckIdNumberAlreadyExists(SaveProxyCommand command) {
if (command.Id > 0)
return true;
var existingIdNumbers = new[] {
1, 2, 3, 4
};
// This is a fudge, but you'd make your db call here
var isNewNumber = !(await Task.FromResult(existingIdNumbers.Contains(command.IdNumber)));
return isNewNumber;
}
}
I didn't include the call to the database, as that's not part of your problem. There are a couple of things of note here:
You're not setting the .WithName annotation method, but when you're setting up a validation rule for an object you have to do this, as FluentValidation expects you to specify specific properties to be validated by default, if you pass in an entire object it just doesn't know how to report errors back.
Must/MustAsync need to return a bool/Task<bool> instead of a custom object. To get around this, you can specify a custom state to be returned when failing validation.
You can then get access to this like this:
var sut = new SaveProxyCommand { Id = 0, IdNumber = 3 };
var validator = new SaveProxyCommandValidator();
var result = validator.ValidateAsync(sut).GetAwaiter().GetResult();
var ValidationFailures = result.Errors?.Select(s => s.CustomState).Cast<ValidationFailure>();
The above does not take into account empty collections, it's just an example of how to dig into the object graph to retrieve custom state.
As a suggestion, fluentvalidation works best if you set up individual rules per property, instead of validating the entire object. My take on this would be something like this:
public class SaveProxyCommandValidator : AbstractValidator<SaveProxyCommand>{
public SaveProxyCommandValidator()
{
RuleFor(o => o.IdNumber).MustAsync(CheckIdNumberAlreadyExists)
.Unless(o => o.Id > 0)
.WithState(o => new ValidationFailure(nameof(o.IdNumber), "Id Number Already Exist"));
}
private static async Task<bool> CheckIdNumberAlreadyExists(int numberToEvaluate) {
var existingIdNumbers = new[] {
1, 2, 3, 4
};
// This is a fudge, but you'd make your db call here
var isNewNumber = !(await Task.FromResult(existingIdNumbers.Contains(numberToEvaluate)));
return isNewNumber;
}
}
This read more like a narrative, it uses the .Unless construct to only run the rule if Id is not more than 0, and does not require the evaluation of the entire object.

set type dynamically using Createmany

I have different types of documents that are derived from a base type called Topic. I'd like to use:
Client.Bulk(b => b.CreateMany(documents)
to be able to process all the documents with a single call to Bulk, how can I set the type for each document?
Here is a snippet of code:
public IEnumerable<IBulkResponse> CreateBulkTopics(IEnumerable<Topic> topics)
{
var results = new List<IBulkResponse>();
results.Add(IndexDocuments(TopicFactory.ConvertDrugsToDocuments(topics)));
results.Add(IndexDocuments(TopicFactory.ConvertTreatmentSummariesToDocuments(topics)));
return results;
}
public IBulkResponse IndexDocuments(IEnumerable<Common.Elastic.Models.Topic> documents)
{
return ElasticConnector.Client.Bulk(b => b.CreateMany(documents));
}
The problem at this minute is all the documents are being stored as "topic" as opposed to the derived types such as drugs and treatmentsummaries.
How many types inherit from Topic? Are they constant and small? Then something like this can help. Lets say TopicA and TopicB inherit from Topic:
public IEnumerable<IBulkResponse> IndexDocuments(IEnumerable<Common.Elastic.Models.Topic> documents)
{
yield return ElasticConnector.Client.Bulk(b => b.CreateMany(documents.OfType<TopicA>()));
yield return ElasticConnector.Client.Bulk(b => b.CreateMany(documents.OfType<TopicB>()));
}
and then in CreateBulkTopics:
results.AddRange(IndexDocuments(....
Of course this is only effective if the number of subclasses is small and available to this code. Otherwise, you can use reflection to achieve the same result. The sample code is a bit more complex, but tell me if you need it. Also, this will degrade performance in case the number of subclasses is very high, as it will send each type in a separate request to Bulk api. I can think of no better apprach in the client.
EDIT: This is how you do it using reflection:
class MyClass
{
public IBulkResponse IndexDocuments<T>(IEnumerable<Topic> documents)
where T : Topic
{
var derived = documents.OfType<T>();
return ElasticConnector.Client.Bulk(b => b.CreateMany(derived));
}
public IEnumerable<IBulkResponse> IndexDocumentsByType(IEnumerable<Topic> documents)
{
var groups = documents.GroupBy(x => x.GetType());
var method = typeof(MyClass).GetMethod(nameof(IndexDocuments)); //prior to c#6, typeof(MyClass).GetMethod("IndexDocuments")
foreach (var group in groups)
{
var generic = method.MakeGenericMethod(group.Key);
var result = generic.Invoke(this, new object[] { group });
yield return result as IBulkResponse;
}
}
}
class Program
{
static void Main(string[] args)
{
var documents = new Topic[] { new TopicA(), new TopicA(), new TopicB(), new Topic() };
var result = new MyClass().IndexDocumentsByType(documents);
Console.WriteLine(result.Count()); //writes 3
}
}
I've managed to do it using a generic class:
public class IndexOperations<T> where T:Topic
{
public ElasticConnector ElasticConnector { get; set; }
public IndexOperations(ElasticConnector elasticConnector)
{
ElasticConnector = elasticConnector;
}
public IBulkResponse CreateMany(IEnumerable<T> t)
{
return ElasticConnector.Client.Bulk(b => b.CreateMany(t));
}
}
Client code:
var documents = TopicFactory.ConvertToDocuments(topics);
SaveDrugs(documents);
public IBulkResponse SaveDrugs(IEnumerable<Common.Elastic.Models.Topic> documents)
{
var indexOperations = new IndexOperations<Drug>(ElasticConnector);
return indexOperations.CreateMany(documents.OfType<Drug>());
}

Categories

Resources