Asynchronous LINQ - c#

I was looking for an async .Where() but could not find one so after some research I've created one.
public static class LinqExtension
{
public static async Task<IEnumerable<T>> WhereAsync<T>(this IEnumerable<T> source, Func<T, Task<bool>> #delegate)
{
var tasks = source.Select(async t => new
{
Predicate = await #delegate(t).ConfigureAwait(false),
Value = t
}).ToList();
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
IEnumerable<T> typeList = results.Where(pred => pred.Predicate).Select(val => val.Value);
return typeList;
}
}
When I try to use it i get runtime error
Cannot convert implicit type bool to Task and yes it's correct
This is how I've tried
var q = await context.StockHistories.WhereAsync(x => x.ProductId == productId);
I've tried
context.StockHistories.WhereAsync(Task.Run(() => { x => x.ProductId == productId; }));
but getting
Only assignment, call, increment, decrement, and new object
expressions can be used as a statement
Can please someone provide a solution and explain what I am doing wrong?

The async methods for EF are the ones that execute the query. So what you actually want is
var q = await context.StockHistories.Where(x => x.ProductId == productId).ToListAsync();
Basically there isn't an asynchronous Where method because it doesn't make sense to have one because it's just used to generate the actual SQL that will be executed on the DB. The query isn't actually run until you iterate the results, and all the methods that do that have an asynchronous version.

Related

making an foreach lambda command async fails

I want to to update a field by lambda expression like this
packageWorkshopDtos.ForEach(p => p.WorkshopDto.ForEach( u => u.SubCategories = _context.School_Categories.Where(j => j.ParentCategoryId == u.CategoryId)
.Select(c => c.Name).ToList()));
For making this async I did this
packageWorkshopDtos.ForEach( p => p.WorkshopDto.ForEach(async u => u.SubCategories = await _context.School_Categories.Where(j => j.ParentCategoryId == u.CategoryId)
.Select(c => c.Name).ToListAsync()));
but it gives me this error
Message "A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext.
how can I make it async?
The way you've written it, the first ForEach starts executing all of the inner async operations simultaneously, which causes the error.
My advice is: quit using the .ForEach method. It's terrible for readability, can only be used on Lists, and causes confusion like in this question. Switch to foreach instead:
foreach (var packageWorkshopDto in packageWorkshopDtos)
{
foreach (var workshopDto in packageWorkshopDto.WorkshopDto)
{
workshopDto.SubCategories = await _context
.School_Categories
.Where(category => category.ParentCategoryId == workshopDto.CategoryId)
.Select(category => category.Name)
.ToListAsync();
}
}
A side advice is to give meaningful names to your lambda parameters.

Better way to Select to an async Func?

I've been refactoring a common pattern in my project and found it's not as simple as using a LINQ Select to an async function.
For context, here is how it is done currently.
async Task<ICollection<Group>> ExecuteQueryGroupsForDomain(DomainInfo domain, int batchSize, CancellationToken ct)
{
try
{
return await BlahBlahActuallyGoGetGroupsForDomainHere(domain, batchSize, ct);
}
catch (Exception e)
{
return null;
}
}
var executeQueries = new List<Func<CancellationToken, Task<ICollection<Group>>>>();
domains.ForEach(domain =>
executeQueries.Add(async (ct) =>
await ExecuteQueryGroupsForDomain(domain, 123, ct)));
Now if I try to replace the ForEach loop section using LINQ:
var executeQueries = domains.Select(domain =>
async (ct) => await ExecuteQueryGroupsForDomain(domain, 123, ct));
It complains Type arguments cannot be inferred by the usage which leads me to believe I'm not returning anything from the Select, but I clearly am returning the Func I want.
Is there a better way of creating a list of Func's, ideally avoiding explicit casts? Also is there any explanation why the compiler is unable to infer the type when the Select'd async method is clearly telling it what the type should be?
To be clear, I do need to pass the CancellationToken to the Func's because it is a different token than the external one (specifically, it is a linked token tying the external one to another internal one).
The problem is in the returns of the select, for the compiler is not clear what the type of the return is. So, you need to explicitly the type of the return, here are 2 ways:
executeQueries = domains.Select(domain =>
new Func<CancellationToken, Task<ICollection<Group>>>(token =>
this.ExecuteQueryGroupsForDomain(domain, 123, token))).ToList();
executeQueries = domains
.Select<DomainInfo, Func<CancellationToken, Task<ICollection<Group>>>>(domain =>
ct => this.ExecuteQueryGroupsForDomain(domain, 123, ct)).ToList();
======================================================================
EDIT 1:
The compiler can't infer the type from a lambda expression because a lambda is just a shorthand for an anonymous method, not a type. So, you need to be explicitly and indicate the return type of the method, if the return is a base Delegate or other delegate type, like Action, Func, etc. Review this other answer, where explain the error compiler based on the C# 4 spec.
If you need transform your original code in something more readable, I don't think there is another way more readable. Here are other ways the code can be written:
foreach (var domain in domains) {
executeQueries.Add(token => this.ExecuteQueryGroupsForDomain(domain, 123, token));
}
executeQueries.AddRange(domains
.Select(domain => (Func<CancellationToken, Task<ICollection<Group>>>) (token =>
this.ExecuteQueryGroupsForDomain(domain, 123, token))));
executeQueries =
(from domain in domains
select new Func<CancellationToken, Task<ICollection<Group>>>(token =>
this.ExecuteQueryGroupsForDomain(domain, 123, token))).ToList()
Do you really need the Func's?
You can use the following if the actual CancellationToken is already present.
// create and start a Task for each domain
var executeQueryTasks = domains.Select(domain => ExecuteQueryGroupsForDomain(domain, 123, ct));
// wait until all tasks are finished and get the result in an array
var executedQueries = await Task.WhenAll(executeQueryTasks);
You may gain some readability by using an extension method like the one below. It takes the same arguments with the LINQ Select method, but returns task-factories instead of materialized tasks.
public static IEnumerable<Func<CancellationToken, Task<TResult>>> SelectTaskFactory
<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, CancellationToken, Task<TResult>> selector)
{
return source.Select(item =>
{
return new Func<CancellationToken, Task<TResult>>(ct => selector(item, ct));
});
}
Usage example:
var executeQueries = domains.SelectTaskFactory(async (domain, ct) =>
{
return await ExecuteQueryGroupsForDomain(domain, 123, ct);
}).ToList();
The type of the executeQueries variable is List<Func<CancellationToken, Task<ICollection<Group>>>>.

How to add new where condition in IQueryable when selecting a view model?

I am fairly new to C# and .Net, so apologies if something doesn't make sense, I will try my best to explain my problem.
I have two methods which are basically going to use the similar query but with slight differences. So instead of repeating the query in both methods I created a third private method which will return the common part of the query and then the functions can add more clauses in query as they require.
Here is a generic function which returns the IQueryable object with common part of the query
private IQueryable<OfferViewModel> GetOffersQueryForSeller(int sellerId)
{
return Db.Offers
.Where(o => o.Sku.SellerId == sellerId && o.IsActive && !o.IsDiscontinued)
.Select(o => new OfferViewModel
{
Id = o.Id,
Name = o.Sku.Name,
ImageUrl = o.Sku.ImageUrl ?? o.Sku.Upcq.Upc.ImageUrl,
QuantityName = o.Sku.QuantityName
});
}
Following are the two method which are reusing the IQueryable object
public async Task<List<OfferViewModel>> GetSellerOffers(int sellerId)
{
var query = GetOffersQueryForSeller(sellerId);
return await query.ToListAsync();
}
public async Task<List<OfferViewModel>> GetDowngradableSellerOffers(int sellerId)
{
var query = GetOffersQueryForSeller(sellerId);
return await query
.Where(o => o.Sku.Id == monthlySkuId)
.ToListAsync();
}
Now GetSellerOffers works just fine but GetDowngradableSellerOffers throws a run time error with message The specified type member 'Sku' is not supported in LINQ to Entities.. I asked around and one of the guys told me that I cannot add additional where after adding a select which uses a ViewModel because then my records will be mapped to ViewModel and LINQ will attempt to look up props of ViewModel instead of database columns.
Now I have two questions,
In the docs I read Entity Framework will only run query when I try to fetch the results with methods like ToList and if I haven't done that why it wouldn't allow me to apply conditions on database fields/
How can I reuse the common query in my scenario?
How about the following code:
(The type Offer should be replaced by the type of the Elements that Db.Offers holds)
private IQueryable<OfferViewModel> GetOffersQueryForSeller(int sellerId, Func<Offer,bool> whereExtension)
{
return Db.Offers
.Where(o => ... && whereExtension.Invoke(o))
.Select(o => new OfferViewModel { ... });
}
private IQueryable<OfferViewModel> GetOffersQueryForSeller(int sellerId)
{
return GetOffersQueryForSeller(sellerId, (o) => true);
}
And then call it in GetDowngradableSellerOffers like this:
public async Task<List<OfferViewModel>> GetDowngradableSellerOffers(int sellerId)
{
var query = GetOffersQueryForSeller(sellerId, (o) => o.Sku.Id == monthlySkuId);
return await query.ToListAsync();
}

Asynchronous method has no availble extension methods, but resulting variable does have

I'm createing some methods in a standard repository class using MongoDB with c# and async methods and I've stumbled upon some strange behaviour.
In the end it's probably not strange at all, rather the problem lies in my own inexperience with async programming.
Consider this code:
public async Task<T> GetItem<T>(ObjectId id) where T : BaseItemEntity
{
var col = GetTypedCollection<T>();
var model = await col.FindAsync(x => x.Id == id);
return await model.FirstOrDefaultAsync();
}
Now that is totally fine, no compilation errors but I'm actually not sure its's correct calling await 2 times.
Now consider this code:
public async Task<T> GetItem<T>(ObjectId id) where T : BaseItemEntity
{
var col = GetTypedCollection<T>();
var model = await col.FindAsync(x => x.Id == id).FirstOrDefaultAsync();
return model;
}
Thats illegal according to the compiler. It's complaining about FirstOrDefaultAsync() not being an availble method to call. I would have to call .Result first to get access to .FirstOrDefaultAsync().
What is going on here?
var model = await col.FindAsync(x => x.Id == id).FirstOrDefaultAsync();
Should be:
var model = await (await col.FindAsync(x => x.Id == id)).FirstOrDefaultAsync();
This is because FindAsync returns a Task object, which FirstOrDefaultAsync doesn't work for. Awaiting the operation yields the actual result.
This is one of the most annoying things with async/await IMO, as it requires parentheses or variables to hold the awaited result for further processing.
I think it actually should be
var model = await (await col.FindAsync(x => x.Id == id)).FirstOrDefaultAsync();
both Async methods return a Task object, and Tasks don't have FirstOrDefault() method( this is why await in () is required). The other one is needed because this way model is your object and not a Task.

Internal .NET Framework Data Provider error 1025

IQueryable<Organization> query = context.Organizations;
Func<Reservation, bool> predicate = r => !r.IsDeleted;
query.Select(o => new {
Reservations = o.Reservations.Where(predicate)
}).ToList();
this query throws "Internal .NET Framework Data Provider error 1025" exception but the query below does not.
query.Select(o => new {
Reservations = o.Reservations.Where( r => !r.IsDeleted)
}).ToList();
I need to use the first one because I need to check a few if statements for constructing the right predicate. I know that I can not use if statements in this circumstance that is why I pass a delegate as parameter.
How can I make the first query work?
While the other answers are true, note that when trying to use it after a select statement one has to call AsQueryable() explicitly, otherwise the compiler will assume that we are trying to use IEnumerable methods, which expect a Func and not Expression<Func>.
This was probably the issue of the original poster, as otherwise the compiler will complain most of the time that it is looking for Expression<Func> and not Func.
Demo:
The following will fail:
MyContext.MySet.Where(m =>
m.SubCollection.Select(s => s.SubItem).Any(expr))
.Load()
While the following will work:
MyContext.MySet.Where(m =>
m.SubCollection.Select(s => s.SubItem).AsQueryable().Any(expr))
.Load()
After creating the bounty (rats!), I found this answer, which solved my problem. (My problem involved a .Any() call, which is a little more complicated than this question...)
In short, here's your answer:
IQueryable<Organization> query = context.Organizations;
Expression<Func<Reservation, bool>> expr = r => !r.IsDeleted;
query.Select(o => new { Reservations = o.Reservations.Where(expr) })
.ToList();
Read the referenced answer for an explanation of why you need the local variable expr, and you can't directly reference another method of return type Expression<Func<Reservation, bool>>.
Thanks for pinging me. I guess I was on the right track after all.
Anyway, to reiterate, LINQ to Entities (thanks to Jon Skeet for correcting me when I got mixed up in my own thought process in the comments) operates on Expression Trees; it allows for a projection to translate the lambda expression to SQL by the QueryProvider.
Regular Func<> works well for LINQ to Objects.
So in this case, when you're using the Entity Framework, any predicate passed to the EF's IQueryable has to be the Expression<Func<>>.
I just experienced this issue in a different scenario.
I have a static class full of Expression predicates which I can then combine or pass to an EF query. One of them was:
public static Expression<Func<ClientEvent, bool>> ClientHasAttendeeStatus(
IEnumerable<EventEnums.AttendeeStatus> statuses)
{
return ce => ce.Event.AttendeeStatuses
.Where(a => a.ClientId == ce.Client.Id)
.Select(a => a.Status.Value)
.Any(statuses.Contains);
}
This was throwing the 1025 error due to the Contains method group call. The entity framework expected an Expression and found a method group, which resulted in the error. Converting the code to use a lambda (which can be implicitly cast to an Expression) fixed the error
public static Expression<Func<ClientEvent, bool>> ClientHasAttendeeStatus(
IEnumerable<EventEnums.AttendeeStatus> statuses)
{
return ce => ce.Event.AttendeeStatuses
.Where(a => a.ClientId == ce.Client.Id)
.Select(a => a.Status.Value)
.Any(x => statuses.Contains(x));
}
Aside: I then simplified the expression to ce => ce.Event.AttendeeStatuses.Any(a => a.ClientId == ce.Client.Id && statuses.Contains(a.Status.Value));
Had a similar problem. Library of ViewModels that look like this:
public class TagViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public static Expression<Func<SiteTag, TagViewModel>> Select = t => new TagViewModel
{
Id = t.Id,
Name = t.Name,
};
This works:
var tags = await db.Tags.Take(10).Select(TagViewModel.Select)
.ToArrayAsync();
But, this won't compile:
var post = await db.Posts.Take(10)
.Select(p => new {
Post = p,
Tags = p.Tags.Select(pt => pt.Tag).Select(TagViewModel.Select)
})
.ToArrayAsync();
Because the second .Select is a mess - the first one is actually called off of an ICollection, which is not IQueryable, so it consumes that first Expression as a plain Func, not Expression<Func.... That returns IEnumerable<..., as discussed on this page. So .AsQueryable() to the rescue:
var post = await db.Posts.Take(10)
.Select(p => new {
Post = p,
Tags = p.Tags.Select(pt => pt.Tag).AsQueryable()
.Select(TagViewModel.Select)
})
.ToArrayAsync();
But that creates a new, weirder problem: Either I get Internal Framework...Error 1025, or I get the post variable with a fully loaded .Post property, but the .Tags property has an EF proxy object that seems to be used for Lazy-Loading.
The solution is to control the return type of Tags, by ending use of the Anonymous class:
public class PostViewModel
{
public Post Post { get; set; }
public IEnumerable<TagViewModel> Tags { get; set; }
Now select into this and it all works:
var post = await db.Posts.Take(10)
.Select(p => new PostViewModel {
Post = p,
Tags = p.Tags.Select(pt => pt.Tag).AsQueryable()
.Select(TagViewModel.Select)
})
.ToArrayAsync();

Categories