I am currently having a text index in my data. I am performing a regex search and I want to make sure that the correct index is used. In the mongo shell I just used explain but how could I use explain in the C# driver?
Here's how I perform my query:
public async Task<IEnumerable<GridFSFileInfo>> Find(string fileName)
{
var filter = Builders<GridFSFileInfo>.Filter.Regex(x => x.Filename, $"/.*{fileName}.*/i");
var options = new FindOptions
{
Modifiers = new BsonDocument("$hint", "filename_text")
};
var result = new List<GridFSFileInfo>();
using (var cursor = await Collection.Find(filter, options).ToCursorAsync())
{
while (await cursor.MoveNextAsync())
{
var batch = cursor.Current;
result.AddRange(batch);
}
}
return result;
}
Is there something like Collection.Find(filter, options).Explain() that returns a string or something? I searched on the web and found this: Is there an "Explain Query" for MongoDB Linq? However is seems there is no Explain method...
The modern mongo c# drivers support only one way to configure $explain. You can do it via FindOptions.Modifiers in the same was as you configured $hint above.
var options = new FindOptions
{
Modifiers = new BsonDocument
{
{ "$hint", "filename_text" },
{ "$explain", 1 }
}
};
var cursor = coll.Find(filter, options).As<BsonDocument>().ToCursor();
Pay attention, since $explain completely changes the server response, you should change your output type to BsonDocument(or a type with fields that matches to what explain returns) via As (if it's already not BsonDocument)
Related
I'm trying to learn to use ABP with Blazor and MongoDB. I've got a set of objects in a database that I display as a list but I also want to be able to retrieve objects from the database by the object's GUID and this is where I'm running into problems.
This is the task I wrote to retrieve objects based on a filter from the database:
private async Task GetObjectAsync(string filter)
{
var result = await ObjectAppService.GetListAsync(
new GetObjectListDto
{
MaxResultCount = PageSize,
SkipCount = CurrentPage * PageSize,
Sorting = CurrentSorting,
Filter = filter
}
);
ObjectList = result.Items;
TotalCount = (int)result.TotalCount;
errorVal = TotalCount.ToString();
This is my GetListAsync() method where I retrieve objects from the database according to the filter string:
public async Task<PagedResultDto<ObjectDto>> GetListAsync(GetObjectListDto input)
{
if (input.Sorting.IsNullOrWhiteSpace())
{
input.Sorting = nameof(Object.Payload);
}
var objects = await _objectRepository.GetListAsync(
input.SkipCount,
input.MaxResultCount,
input.Sorting,
input.Filter
);
var totalCount = input.Filter == null
? await _objectRepository.CountAsync()
: await _objectRepository.CountAsync(
** SEARCH METHODS GO HERE **
return new PagedResultDto<ObjectDto>(
totalCount,
ObjectMapper.Map<List<Object>, List<ObjectDto>>(objects)
);
}
These are the search methods I've been trying:
object => object.Name.Contains(input.Filter));
object => object.Id.ToString().Contains(input.Filter));
object => object.Id.Equals(Guid.Parse(input.Filter)));
The first search method (search by name and return any DB results which contain it) works perfectly except for the fact I want to be able to search by GUID. The second search method would be ideal except it throws an exception when I use it:
(System.ArgumentException: Unsupported filter: {document}{_id}.ToString().Contains("70f35018-a504-89f1-51c0-3a04cb49cb97").).
The third search method definitely retrieves something from the database (I have verified) but it doesn't display this result on the page for some reason and I obviously have to enter a complete GUID to return the object (not ideal).
I'm sure this is possible and I'm doing something wrong here but I'm not sure what that is. Can anyone tell me the correct way to search by GUID here? I expected Id.ToString().Contains(Filter) to work.
UPDATE:
I've sort of solved this problem - I needed to look at the MongoObjectRepository.cs file:
public async Task<List<Object>> GetListAsync(
int skipCount,
int maxResultCount,
string sorting,
string filter = null)
{
var queryable = await GetMongoQueryableAsync();
return await queryable
.WhereIf<Object, IMongoQueryable<Object>>(
!filter.IsNullOrWhiteSpace(),
obj => obj.Id.Equals(filter)
)
.OrderBy(sorting)
.As<IMongoQueryable<Object>>()
.Skip(skipCount)
.Take(maxResultCount)
.ToListAsync();
}
For some reason, I was filtering based on the Payload property rather than Id.
I'm still confused, however, why obj => obj.Id.ToString().Contains((filter)) doesn't work - is this not allowed when retrieving items from a database? I've tried this on DateTimeOffset fields as well (e.g. obj => obj.InsertTimestamp.ToString().Contains((filter))) and get similar problems. When I try to add the .FirstOrDefault() to the end of the .WhereIf<Object, IMongoQueryable<Object>>() statement it invalidates the function (has to be chained with all the other methods - maybe that's the problem there?)
I'm trying to update some code to the new version of Mongodb driver for c#, version 2 or newer, but it seems that the CollectionExists() method was deprecated, it used to work in the old version but not anymore. How can I know if a collection exists already with the new driver?
public static IMongoCollection<T> GetCollectionSafe<T>(string collectionName)
{
var db = GetDatabase();
if (!db.CollectionExists(collectionName)) //throws error
{
db.CreateCollection(collectionName);
}
return db.GetCollection<T>(collectionName);
}
GetDatabase() is of type IMongoDatabase . I just want to know if a collection with a certain name exists.
You can check existence of the collection by following code
public async Task<bool> CheckCollection(IMongoDatabase database, string collectionName)
{
var filter = new BsonDocument("name", collectionName);
var collectionCursor = await database.ListCollectionsAsync(new ListCollectionsOptions {Filter = filter});
return await collectionCursor.AnyAsync();
}
P.S. Method GetCollection is safe for using. You don't need to check existence of the collection. It was done by driver itself.
This code fails on my database:
database.ListCollections(new ListCollectionsOptions {
Filter = new BsonDocument { { "name", collectionName } } })
I get the following error message:
The GuidRepresentation for the reader is CSharpLegacy, which requires
the binary sub type to be UuidLegacy, not UuidStandard
The collection in question was created the MongoDb sink for Serilog with GuidRepresentation = CSharpLegacy.
This code works without problems (with mongo driver v. 2.5.0):
database.GetCollection<BsonDocument>(collectionName) != null
In new version of MongoDB.Driver 2.9.3 for checking the collection existence and then create it , you can use this code:
var dbset = typeof(T).Name;
var tables = MongoDb.ListCollectionNames().ToList();
if (!tables.Any(x => x == dbset))
{
MongoDb.CreateCollection(dbset);
}
return MongoDb.GetCollection<T>(dbset);
Have you tried by using ?
db.GetCollection("collectionName").Exists()
How to check if collection exists in MongoDB using C# driver?
I’m looking to take a dynamic object from a third party API & convert to my C# object with minimal code.
Sort of like what Dapper does with a SQL statement results to a C# object. Is there a library that would do this?
I can do it manually like below, but it seems like alot of code to write everytime I want to retrieve from this API.
public IEnumerable<Requirement> Get()
{
dynamic requirementsSelect;
requirementsSelect = _RequirementsRepository.GetRequirements();
IList<Requirement> Requirements = new List<Requirement>();
foreach (var req in requirementsSelect)
{
Requirement requirement = new Requirement();
requirement.Text = req.Text;
requirement.Number = req.Number;
Requirements.Add(requirement);
foreach (var node in req.Phrases)
{
Requirement reqNode = new Requirement();
reqNode.Text = node.Text;
reqNode.Number = node.Number;
requirement.Nodes.Add(reqNode);
}
return Requirements;
}
Thanks
I ended up returning the third party as JSON as follows. In my specific case the JSON wasn't showing the properties. I resolved by changing the dynamic object to IEnumerable and I was then able to specify which properties I wanted to retrieve using .Select.
IEnumerable<dynamic> requirements = _RequirementsRepository.GetRequirements();
return JsonConvert.SerializeObject(new
{
Requirement = requirements.Select(x => x.Text),
Number = requirements.Select(x => x.Number),
});
Thanks!
I fail to get GridFSFileInfo by ObjectID, but succeed by filename,
and the error message is:
Unable to determine the serialization information for x=>x.Id
string objectID = ObjectIDTxt.Text.Trim();
GridFSBucketOptions bucketOptions = new GridFSBucketOptions();
bucketOptions.BucketName = "myBucket";
ObjectId gridfsObjectID = new ObjectId(objectID);
//by filename will succeed
//var filter = Builders<GridFSFileInfo>.Filter.Eq(x => x.Filename, "myfilename.pdf");
//by ObjectID will fail
var filter = Builders<GridFSFileInfo>.Filter.Eq(x=>x.Id,gridfsObjectID);
var findOptions = new GridFSFindOptions();
findOptions.Limit = 1;
var myBucket = new GridFSBucket(_database, bucketOptions);
using (var taskOfCursor = Task.Run(() => myBucket.FindAsync(filter, findOptions)))
{
var taskOfList = Task.Run(() => taskOfCursor.Result.ToListAsync());
GridFSFileInfo fileInfo = taskOfList.Result.FirstOrDefault();
if (fileInfo != null)
{
FileNameLbl.Text = fileInfo.Filename;
}
}
I'm using Mongodb 3.0,c# driver 2.1,wird tiger storage engine.
Forgive me about the use of many 'Task.Run()',because for some reason I need to sync call async mongo methods.
Any suggestions will be appreciated...
thx
Unable to determine the serialization information for x=>x.Id
As the error suggests, you can't use x.Id inside your query in this way. The lambda expression provided is used to retrieve the name of the property and it doesn't understand what x.Id is.
You may try this:
var filter = Builders<GridFSFileInfo>.Filter.Eq("_id", gridfsObjectID);
which uses this overload of the Eq method and performs the implicit conversion from String to FieldDefinition.
Expressions seem a bit puzzling for me as well, but you may find more information related to Expression in the answers to this question: Why would you use Expression> rather than Func?
You can add the lambda syntax directly in Find method:
myBucket.FindAsync(x => x.Id == new MongoDB.Bson.ObjectId(objectID), findOptions)
I would like to allowDiskUse:true. However I could not found any example which explain allowDiskUse enabling for MongoDB C# driver.
How can I enable allowDiskUse in MongoDB C# driver?
My sample code like that
var pipeline = new[] { match, project, group, limit, sort, allow };
List<SMBMostInfluentialUser> result = db
.GetCollection<SMBTwitterStatus>("TwitterStatus")
.Aggregate(pipeline).ResultDocuments.Select(x =>
new User
{
Influence = Convert.ToDouble(x["Influence"]),
User = new SMBUser((BsonDocument)x["User"])
}).ToList();
Use the other overload of Aggregate that takes an AggregateArgs parameter and gives you more control over the operation, including setting AllowDiskUse:
var pipeline = new BsonDocument[0]; // replace with a real pipeline
var aggregateArgs = new AggregateArgs { AllowDiskUse = true, Pipeline = pipeline };
var aggregateResult = collection.Aggregate(aggregateArgs);
var users = aggregateResult.Select(x =>
new User
{
Influence = x["Influence"].ToDouble(),
User = new SMBUser(x["user"].AsBsonDocument)
}).ToList();
Note that the return type of this overload of Aggregate is IEnumerable<BsonDocument> so you no longer have to use the ResultDocuments property.
Just to be clear, the Select is being executed client side. You might be able to arrange it so that the documents coming out of your aggregation pipeline can be directly deserialized into instances of one of your classes.
For more recent versions of MongoDB C# driver (not sure starting with what version), the syntax is:
var aggregateOptions = new AggregateOptions{ AllowDiskUse = true};
var aggregateResult = collection.Aggregate(aggregateOptions);