Applying filter GridFS MongoDb C# - c#

I am trying to filter my data based on the two factors. The first factor is the docID(object Id) and the second factor is DocType which is stored as metadata. The sample code I am pasting.I am also attaching the sample DB how it looks like.
public async Task<ActionResult> DeleteDocument([FromRoute] int docType, [FromRoute] string docId)
{
try
{
var filter = Builders<GridFSFileInfo>.Filter.And(
Builders<GridFSFileInfo>.Filter.Eq(x => x.Metadata.GetValue("DocType"), docType),
Builders<GridFSFileInfo>.Filter.Eq(x => x.Id.ToString(), docId));
using (var cursor = await Bucket.FindAsync(filter))
{
var fileInfos = (await cursor.ToListAsync());
foreach (GridFSFileInfo fileInfo in fileInfos)
{
foreach (BsonElement bsonE in fileInfo.Metadata.ToList())
{
Console.WriteLine(fileInfo.Filename);
}
}
}
}
But I am not getting the correct result it giving me errors. Can anyone please point out what I am doing wrong or how to write filters based on metadata information.

Looks like they're a few problems with the above code. The MongoDB Driver is struggling with converting these expressions into a MongoDB Query.
.Eq(x => x.Metadata.GetValue("DocType"), docType)
.Eq(x => x.Id.ToString(), docId)
This is because you need to use the indexer to accessing fields of the metadata, also you'll need to change docId into the correct type before comparing it against the Id field.
Try the following filter out.
var docType = 1;
var docId = ObjectId.Parse("5f3ce002796ba13443aa4bc5");
var filter =
Builders<GridFSFileInfo<ObjectId>>.Filter.Eq(x => x.Metadata["DocType"], docType)
& Builders<GridFSFileInfo<ObjectId>>.Filter.Eq(x => x.Id, docId);

Related

MongoDb Update one field using BulkWriteAsync

I am trying to add one property in some of my documents (later I will be updating it).
My code looks like below:
public async Task<bool> InsertTagsToPeople(IEnumerable<People> toInsert)
{
var collection = _mongoClient.GetPeopleDetailsCollection();
try
{
var updates = new List<WriteModel<People>>();
foreach (var doc in toInsert)
{
var filter = Builders<People>.Filter.And(
Builders<People>.Filter.Eq(x => x.Address, doc.Address),
Builders<People>.Filter.Eq(x => x.Type, doc.Type));
var update = Builders<People>.Update.Set(x => x.Tags, doc.Tags);
updates.Add(new UpdateOneModel<People>(filter, update){IsUpsert = false});
}
var result = await collection.BulkWriteAsync(updates);
return true;
}
}
Problem with this code is that it is not setting Tags property. Nothing on Database is changing.
In result object I have properties like this:
When I change IsUpsert = true it throws error
Category : "DuplicateKey", Code : 11000, Message : "E11000 duplicate
key error collection:
I don`t understand why it is trying to update whole model instead just one property. But I suppose that IsUpsert do not matter here. Update.Set should do the work.

How to get the entire list instead of only the first result? Net Core 3.1

The following functions return only the first row of the database, would you please guide me get the entire rows please?
My Function :
private List<Phrases_Clp> PhrasesStringToListPhrases_Clp(string phrases_codes)
{
List<Phrases_Clp> phrases_Clps = new List<Phrases_Clp>();
if (!String.IsNullOrEmpty(phrases_codes))
{
if (phrases_codes.Contains(","))
{
string[] listprhases = phrases_codes.Split(",");
foreach (var item in listprhases)
{
var mentiontoinsert = _context.Phrases_Clps.FirstOrDefault(m =>
m.Phrase_Numero == item);
phrases_Clps.Add(mentiontoinsert);
}
}
else
{
var mentiontoinsert = _context.Phrases_Clps.FirstOrDefault(m =>
m.Phrase_Numero == phrases_codes);
phrases_Clps.Add(mentiontoinsert);
}
}
return phrases_Clps;
}
UPDATE :
It still gives me the first result. I wonder if I should do changes in my view as I display information from another table which correspond the cell values in the current table.
Table Identification_Produit
Table Phrases Clp
Current Output:
Desired Output is to display 'Mentions Dangers' of each Product.
GET:
//GET: EvaluationRisque/Create
[Authorize(Roles = "Administrateur, Modificateur")]
public async Task<IActionResult> Create()
{
List<Models.dbo.equipements_protection.Equipement_Protection> listsequipement = _context.Equipement_Protections.ToList();
Create_Viewbags();
var model = new EvaluationRisquesViewModel();
var produitUtiliseByProduitId = await _context.Identification_Produit.FirstOrDefaultAsync();
model.MentionsDanger = produitUtiliseByProduitId;
model.List_Mentions_Danger = PhrasesStringToListPhrases_Clp(model.MentionsDanger.Mentions_Danger);
return View(model);
}
Bootstrap Model in the View Page:
<td>#foreach (var mention_danger in Model.List_Mentions_Danger)
{
<p> #mention_danger.Phrase_Numero : #mention_danger.Phrase_Libelle
</p>}
</td>
Change FirstOrDefault(...) to .Where(...).ToList() (or use phrases_Clps.AddRange( _context.Phrases_Clps.Where(...))).
But actually there is no need to make multiple requests to the database and you can skip conditional splitting, so something like following should work:
List<Phrases_Clp> phrases_Clps = null;
if (!string.IsNullOrEmpty(phrases_codes))
{
string[] listprhases = phrases_codes.Split(","); // will be array with one item if there are no commas in string
phrases_Clps = _context.Phrases_Clps
.Where(m => listprhases.Contains(m.Phrase_Numero))
.ToList();
}
return phrases_Clps ?? new List<Phrases_Clp>();
Because you are using FirstOrDefault function in this line:
var mentiontoinsert = _context.Phrases_Clps.FirstOrDefault(m => m.Phrase_Numero == item);
replace it with Where function.
Thinking that to utilize the query as IQueryable.
Updated:
Thanks for #GuruStron (Guru/Sensei)'s opinion and for pointing out the issue from my previous answer.
In short,
Exit the function with an empty List if phrases_codes is an empty string or null.
Working with IQueryable to prepare the different search criteria and defer the execution.
Once the query is prepared, execute the query via .ToList().
if (String.IsNullOrEmpty(phrases_codes))
return new List<Phrases_Clp>();
IQueryable<Phrases_Clp> query = _context.Phrases_Clps;
if (phrases_codes.Contains(","))
{
string[] listprhases = phrases_codes.Split(",");
query = query
.Where(x => listprhases.Contains(x.Phrase_Numero));
}
else
{
query = query
.Where(x => x.Phrase_Numero == phrases_codes);
}
return query.ToList();
This is my minimalist proposal
private List<Phrases_Clp> PhrasesStringToListPhrases_Clp(string phrases_codes)
{
var listprhases = phrases_codes != null ? phrases_codes.Split(",") : new string[0];
return _context.Phrases_Clps.Where(x => listprhases.Contains(x.Phrase_Numero)).ToList();
}

How to convert SQLQuery to SortedList through EF6

I have an Entity Framework 6 class called Materials, which is reflected in my database as a table with the same name. Using a parent parameter, I need to return a sorted list of materials from a SQL Query, so that I can later check that edits the user makes do not affect the order. My SQL is a stored procedure that looks like this:
CREATE PROC [dbo].[GET_SortedMaterials](#FinishedGoodCode VARCHAR(50))
AS
SELECT
ROW_NUMBER() OVER (ORDER BY Component.Percentage_of_Parent DESC,Material.Material) AS _sortField
,Material.*
FROM
Components AS Component
INNER JOIN Materials AS Material ON Component.Child_Material = Material.Material
WHERE
Component.Parent_Code = #FinishedGoodCode
ORDER BY
Component.Percentage_of_Parent DESC
,Material.Material
As you can see, the orderby field is not included in the Material. For this reason, I felt I could not return just a set of Material objects and still keep the sorting - I have performed the ordering in SQL and added the _sortField (I think that field may be a bad idea).
My C# code to read the SQL looks like this:
public async Task<SortedList<int, Materials>> GET_SortedMaterials(IProgress<Report> progress, string finishedGoodCode)
{
try
{
var report = new Report { Message = "Retrieving Sorted Materials", NewLine = true, StatusCode = Enums.StatusCode.Working };
progress.Report(report);
using (var context = new DBContext())
{
var ingredientList = await context.Database.SqlQuery<(int _sortField,Materials mat)>("[app].[GET_Customers]").ToListAsync();
var sorted = new SortedList<int, Raw_Materials>();
foreach (var (_sortField, mat) in ingredientList.OrderBy(x=>x._sortField))
{
sorted.Add(_sortField, mat);
}
return sorted;
}
}
catch (Exception ex)
{ [EXCLUDED CODE]
}
}
When the code executes, I get the correct number of rows returned, but I do not get a Sorted list where the Key corresponds to the _sortField value and the Value to the Material value. I have tried various different versions of basically the same code and I cannot get the script to return a list of materials with information about their sorting, instead, the conversion to EF class fails entirely and I only get null values back:
Any advice about how to return a sorted list from SQL and maintain the sorting in C#, when the sort field is not in the return values would be very gratefully received.
use
var ingredientList = await context.Database.SqlQuery<Materials>("[app].[GET_Customers]").Select((mat, _sortField) => (_sortField, mat)).ToDictionary(x => x._sortField, x => x.mat);
or if you want async load use
var ingredientList = await context.Database.SqlQuery<Materials>("[app].[GET_Customers]").ToListAsync().Result.Select((mat, _sortField) => (_sortField, mat)).ToDictionary(x => x._sortField, x => x.mat);
full code
public async Task<SortedList<int, Materials>> GET_SortedMaterials(IProgress<Report> progress, string finishedGoodCode)
{
try
{
var report = new Report { Message = "Retrieving Sorted Materials", NewLine = true, StatusCode = Enums.StatusCode.Working };
progress.Report(report);
using (var context = new DBContext())
{
var ingredientList = await context.Database.SqlQuery<Materials>("[app].[GET_Customers]").ToListAsync().Result.Select((mat, _sortField) => (_sortField, mat)).ToDictionary(x => x._sortField, x => x.mat);
var sorted = new SortedList<int, Raw_Materials>();
foreach (var item in ingredientList.OrderBy(x => x.Key))
{
sorted.Add(item.Key, item.Value);
}
return sorted;
}
}
catch (Exception ex)
{
[EXCLUDED CODE]
}
}

How to delete several documents by ID in one operation using Elasticsearch Nest

I am building some abstraction functions for my application to call, which will hit elasticsearch through Nest. One of such functions is a Delete(string id) call, which is easy to accomplish. I have done this as follows:
public void Delete(string id)
{
esClient.Delete(id);
}
Now let's say I want to do the same thing, but operate on several documents simultaneously. My original hunch was to do something like this:
public void Delete(IEnumerable<string> ids)
{
esClient.DeleteMany(ids); // won't compile
}
As my comment states, doing this won't compile. What is the proper way of batch deleting documents by ID in Nest?
To use esClient.DeleteMany(..) you have to pass collection of objects to delete.
var objectsToDelete = new List<YourType> {.. };
var bulkResponse = client.DeleteMany<YourType>(objectsToDelete);
You can get around this by using following code:
var ids = new List<string> {"1", "2", "3"};
var bulkResponse = client.DeleteMany<YourType>(ids.Select(x => new YourType { Id = x }));
Third option, use bulk delete:
var bulkResponse = client.Bulk(new BulkRequest
{
Operations = ids.Select(x => new BulkDeleteOperation<YourType>(x)).Cast<IBulkOperation>().ToList()
});
I was working on a .NET client for ElasticSearch 5.x, and I was fortunate to have the following code running (as well as succeeding all unit tests) for bulk deletion using ID's:
//IList<string> ids = ...
var descriptor = new BulkDescriptor();
foreach (var id in ids.Where(x => !string.IsNullOrWhiteSpace(x)))
descriptor.Delete<T>(x => x
.Id(id))
.Refresh(Refresh.WaitFor);
var response = await _client.BulkAsync(descriptor);

Query MongoDB Using 'ObjectId'

I have inserted documents into MongoDB without an id. And I want to retrieve them by searching through their MongoDB ObjectId, that has been assigned in default.
Here is my attempt-
var query_id = Query.EQ("_id", "50ed4e7d5baffd13a44d0153");
var entity = dbCollection.FindOne(query_id);
return entity.ToString();
And I get following error-
A first chance exception of type 'System.NullReferenceException' occurred
What is the problem?
You need to create an instance of ObjectId and then query using that instance, otherwise your query compares ObjectIds to string and fails to find matching documents.
This should work:
var query_id = Query.EQ("_id", ObjectId.Parse("50ed4e7d5baffd13a44d0153"));
var entity = dbCollection.FindOne(query_id);
return entity.ToString();
In C# for latest official MongoDB.Driver write this-
var filter_id = Builders<MODEL_NAME>.Filter.Eq("id", ObjectId.Parse("50ed4e7d5baffd13a44d0153"));
var entity = dbCollection.Find(filter).FirstOrDefault();
return entity.ToString();
We can accomplish the same result without converting id from string to ObjectId. But then, we will have to add [BsonRepresentation(BsonType.ObjectId)] before id attribute in the model class.
The code can even be further simplified using lambda expression-
var entity = dbCollection.Find(document => document.id == "50ed4e7d5baffd13a44d0153").FirstOrDefault();
return entity.ToString();
If you're here in 2018 and want copy/paste code that still works or pure string syntax;
[Fact]
public async Task QueryUsingObjectId()
{
var filter = Builders<CosmosParkingFactory>.Filter.Eq("_id", new ObjectId("5b57516fd16cb04bfc35fcc6"));
var entity = stocksCollection.Find(filter);
var stock = await entity.SingleOrDefaultAsync();
Assert.NotNull(stock);
var idString = "5b57516fd16cb04bfc35fcc6";
var stringFilter = "{ _id: ObjectId('" + idString + "') }";
var entityStringFiltered = stocksCollection.Find(stringFilter);
var stockStringFiltered = await entityStringFiltered.SingleOrDefaultAsync();
Assert.NotNull(stockStringFiltered);
}
The selected answer is correct. For anyone confused by the Query.EQ, here is another way to write a basic update (updates the entire mongodb document):
string mongoDocumentID = "123455666767778";
var query = new QueryDocument("_id", ObjectId.Parse(mongoDocumentID));
var update = new UpdateDocument { { "$set", documentToSave } };
mongoCollection.Update(query, update, UpdateFlags.Multi);
The ObjectId object is needed when you want to actually search by object ID, otherwise it is comparing string to objectid type, and it won't match. Mongo is very type-strict in this way, regardless if the field name is correct.
You can also do it this way, its
public static ObjectId GetInternalId(string id)
{
if (!ObjectId.TryParse(id, out ObjectId internalId))
internalId = ObjectId.Empty;
return internalId;
}
then in your method you can do something like this
ObjectId internalId = GetMongoId.GetInternalId(id);
return await YourContext.YourTable.Find(c => c.InternalId == internalId).FirstOrDefaultAsync();
Note: id param in GetInternalId its a parameter on that method. In case you need as this:
public async Task<YourTable> Find(string id)
{
ObjectId internalId = GetMongoId.GetInternalId(id);
return await YourContext.YourTable.Find(c => c.InternalId == internalId).FirstOrDefaultAsync();
}
Hope it helps also.

Categories

Resources