Getting a single object from mongodb in C# - c#

I've picked up a piece of code that is using the MongoDB driver like this to get a single object from a collection...this can't be right, can it? Is there a better way of getting this?
IMongoCollection<ApplicationUser> userCollection;
....
userCollection.FindAsync(x => x.Id == inputId).Result.ToListAsync().Result.Single();

Yes, there is.
First of all don't use FindAsync, use Find instead. On the IFindFluent result use the SingleAsync extension method and await the returned task inside an async method:
async Task MainAsync()
{
IMongoCollection<ApplicationUser> userCollection = ...;
var applicationUser = await userCollection.Find(_ => _.Id == inputId).SingleAsync();
}
The new driver uses async-await exclusively. Don't block on it by using Task.Result.

You should limit your query before executing, otherwise you will first find all results and then only read one of it.
You could either specify the limit using FindOptions in FindAsync, or use the fluent syntax to limit the query before executing it:
var results = await userCollection.Find(x => x.Id == inputId).Limit(1).ToListAsync();
ApplicationUser singleResult = results.FirstOrDefault();
The result from ToListAsync will be a list but since you limited the number of results to 1, that list will only have a single result which you can access using Linq.

In newer versions of MongoDB Find() is deprecated, so you can either use
collection.FindSync(o => o.Id == myId).Single()
or
collection.FindAsync(o => o.Id == myId).Result.Single()
You can also use SingleOrDefault(), which returns null if no match was found instead of throwing an exception.

I could not get the method:
coll.Find(_ => _.Id == inputId).SingleAsync();
To work as I was getting the error
InvalidOperationException: Sequence contains more than one element c#
So I ended up using .FirstOrDefault()
Example:
public FooClass GetFirstFooDocument(string templatename)
{
var coll = db.GetCollection<FooClass>("foo");
FooClass foo = coll.Find(_ => _.TemplateName == templatename).FirstOrDefault();
return foo;
}

Related

How to ensure linq statement will not fail

what's correct way to ensure this linq statment will be executed as expect, by that I mean all params will be populated (not null values).
Here is example:
public async Task<IEnumerable<articles>> GetArticlesByStatus(articleRequest request)
{
var query = await _context.article.AsNoTracking()
.Where(x => x.ArticleStatusId == (int)request.ArticleStatus).ToListAsync();
}
One thing might be issue and that is request.ArticleStatus, if that's null than this statment wont be correct..
I've tried with something like this:
public async Task<IEnumerable<articles>> GetArticlesByStatus(articleRequest request)
{
if(request.Status.HasValue) // Included .HasValue but I guess there's more elegant one line solution?
{
var query = await _context.article.AsNoTracking()
.Where(x => x.ArticleStatusId == (int)request.ArticleStatus).ToListAsync();
}
}
Thanks
Cheers
If the method accepts an ArticleStatus instead of an articleRequest, it won't fail, i.e. you will never get an InvalidCastException at runtime, unless ArticleStatus is nullable (ArticleStatus?) or has another underlying type than int (which is the default underlying type for enums):
public async Task<IEnumerable<articles>> GetArticlesByStatus(ArticleStatus status)
{
var query = await _context.article.AsNoTracking()
.Where(x => x.ArticleStatusId == (int)status).ToListAsync();
}
I've assumed that ArticleStatus is an enum.
If you keep the articleRequest parameter and articleRequest is a nullable reference type, you should check whether it's null before you try to access its Status property:
Where(x => request == null || x.ArticleStatusId == (int)request.Status)
Beware that ORMs like Entity Framework may not be able to translate your predicate(s) to valid SQL.
If request.ArticleStatus is a Nullable<ArticleStatus> then you could just cast it to a Nullable<int> in your query.
public async Task<IEnumerable<articles>> GetArticlesByStatus(articleRequest request)
{
var query = await _context.article.AsNoTracking()
.Where(x => x.ArticleStatusId == (int?)request.ArticleStatus).ToListAsync();
return query;
}
This assumes that request isn't null, which is something you'd need to check for.
That said, personally I prefer what you posted. I'm not sure what you mean by "more elegant", but if you know in advance from the state of request that the query won't have any results... why run it? Why not just skip sending a query to the database and just return Enumerable.Empty<articles>() instead?

mongo Db unsupported filter type when using Any() or Contains() - Are there any workarounds / fixes?

i have seen other threads for this kind of question but none of the highlighted answers work for me.
The piece of code that throws the error is this
List<Distribution> distributionPublishQueueList = (mongoDb.GetCollection<Distribution>().Find(Builders<Distribution>.Filter.And(
Builders<Distribution>.Filter.Where(x => x.Status == EntityStatus.Ok),
Builders<Distribution>.Filter.Where(x => x.IsActive),
Builders<Distribution>.Filter.Where(x => distinctDistributionIdInPublishQueueList.Contains(x.Id))))).ToList();
The original code was this:
List<Distribution> distributionPublishQueueList =
(await mongoDb.GetCollection<Distribution>()
.FindAsync(x => x.Status == EntityStatus.Ok
&& x.IsActive
&& distinctDistributionIdInPublishQueueList.Contains(x.Id)))
.ToList();
but i tried to make it more mongo friendly. Both pieces of code above are the same. The list distinctDistributionIdInPublishQueueList is a list of distribution Id's which are strings. So i am trying to find all distributions which Id is inside of that list + the other 2 filters. When i use the contains inside of the filter definition it throws an Unsupported filter exception. But the following code works because i bring the list into local memory and use LINQ against it:
List<Distribution> distributionPublishQueueList = (await mongoDb.GetCollection<Distribution>().FindAsync(x => x.Status == EntityStatus.Ok && x.IsActive)).ToList();
distributionPublishQueueList = distributionPublishQueueList.Where(x => distinctDistributionIdInPublishQueueList.Contains(x.Id)).ToList();
I need to be able to not do this in local memory due to the amount of Distributions that are present in the database. Is there a workaround to using Contains and Any. I have also tried using MongoCSharpDriver In statement and Builders.Filter.In and other variations.
An example error would be as follows. This is the code that is used.
List<Asset> assetList = (await mongoDb.GetCollection<Asset>().FindAsync(
asset => extractAssetsFromContentService.ExtractAssetFromDraftContent(contentAsMarkdown)
.Any(extractedAsset => extractedAsset.AssetId == asset.Id))).ToList();
System.ArgumentException : Unsupported filter: Any(value(System.Collections.Generic.List`1[DocWorks.Common.Transformation.Model.ExtractedAssetModel]).Where(({document}{AssetId} == {document}{_id}))).
this would be the same error except the Any would be 'Contains' when using contains instead of any. Similar to a distribution i cannot bring the assets into local memory. All entities share the same base class which Stores the Ids as follows:
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
You can use any of the following solutions;
var filter = Builders<Distribution>.Filter.ElemMatch(x => x.Distincts, i => distinctIdList.Contains(i.Id));
var results = await (await _collection.FindAsync(filter)).ToListAsync();
or
Expression<Func<Distribution, bool>> filter = x => districtIdList.Contains(x.Id);
var results = await (await _collection.FindAsync(filter)).ToListAsync();
These two solutions work fine in my projects. I have a library where you can apply these usages. When you send an expression to any of the get methods here, it will work. For example;
var distributionPublishQueueList = await _distributionRepository.GetAllAsync(x => distinctDistributionIdInPublishQueueList.Contains(x.Id));
You can see the documentation from here.

EfCore ToListAsync() Throw exception with where condition

I have a problem with an async task that get items from the database with a specific condition
When i call the ToListAsync() i get this error
Expression of type 'System.Collections.Generic.IAsyncEnumerable1[System.Guid]' cannot be used for constructor parameter of type 'System.Collections.Generic.IEnumerable1[System.Guid]'
Parameter name: arguments[0]
This is the code snippet:
public async Task<IEnumerable<Parent>> GetItems(List<Guid> TypeIds)
{
var items = context.Parent.Include(x => x.Child).Where(x => new HashSet<Guid>(x.Child.Select(y => y.TypeId).Distinct()).SetEquals(new HashSet<Guid>(TypeIds)));
return await items.Include(x=> x.Child).ToListAsync();
}
If I implement this method not async I won't get the error and all works.
You can't have a Where lambda like that for Entity Framework, remember that the expression is going to get converted to SQL which has no idea what a HashSet is. You are probably looking for something like this instead:
var items = context.Parent
.Include(x => x.Child)
.Where(x => x.Child.Any(y => TypeIds.Contains(y.TypeId)));

How can I use LINQ to just return one row from a selection?

I have this code:
var wordForms = await db.WordForms
.Where(w => w.Text == word)
.AsNoTracking()
.ToListAsync();
It returns a list but there's only ever one entry as Text is unique. How can I make this just retrieve one word only?
use FirstOrDefaultAsync as your final LINQ method:
var wordForms = await db.WordForms
.AsNoTracking()
.FirstOrDefaultAsync(w => w.Text == word)
If I understand correctly, you want to return single element right away. I do this by:
//When there is multiple entries expected but I only need the first
var wordForms = await db.WordForms
.FirstOrDefaultAsync(w => w.Text == word);
or
//When I am sure only one entry exist
var wordForms = await db.WordForms
.SingleOrDefaultAsync(w => w.Text == word);
If you do not need to await you can just use the default extension methods without await.
For Async SingleOrDefault and Async FirstOrDefault you must include Entity framework 6.
Why is there no SingleOrDefaultAsync for IQueryables?
https://msdn.microsoft.com/en-us/library/dn220208(v=vs.113).aspx
I would imagine that your terminal operation needs to be any of FirstAsync, FirstOrDefaultAsync, SingleAsync or SingleOrDefaultAsync.
I haven't checked, but I'm presuming that all of those variants exist.
So you just want the first value from WordForms that have Text==word?
var wordForms = (from c in db.WordForms
where c.Text==word
select c).FirstOrDefault()

MongoDB .NET driver find all : How to write it better?

I am able to query all the items of a collection using 2 approaches
a)
var findAll = await Context.ItemsCollection.FindAsync(_ => true);
var res = await findAll.ToListAsync();
b)
var res = await.Context.ItemsCollection.Find(_ => true).ToListAsync();
Is there a real difference between them?Which one should I prefer ?
There's no real difference. It will eventually behave the same.
Find doesn't execute the query, while FindAsync does, but using ToListAsync makes that difference irrelevant.

Categories

Resources