making an foreach lambda command async fails - c#

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.

Related

EF core ThenInclude Select says "Lambda expression used inside Include is not valid"

var test = await Db.Tests.Include(test => test.Questions)
.ThenInclude(ques => ques.Choices.Select(
ch => new {
ch.Id, ch.OptionName,
ch.OptionText, ch.OptionDetails,
ch.IsAnswer, ch.QuestionId
})).AsNoTracking()
.FirstOrDefaultAsync(z => z.TestId == testId);
Without Select clause it works fine. But I don't need all the Choices properties, so I try to select some of the properties by using Select clause.
It throughs this error:
System.InvalidOperationException: Lambda expression used inside
Include is not valid.
Can anybody tell me what's wrong here?
I think you want something like:
var test = await Db.Tests
.Where(test => test.TestId == testId)
.Include(test => test.Questions) // Shouldn't need this
.ThenInclude(ques => ques.Choices) // Shouldn't need this
.SelectMany(test => test.Questions.SelectMany(ques => ques.Choices.Select(ch => new {
ch.Id,
ch.OptionName,
ch.OptionText,
ch.OptionDetails,
ch.IsAnswer,
ch.QuestionId
}))))
.AsNoTracking();
This will give you all the choices for a particular test.
But... since you're materializing the results as a lambda, you actually shouldn't need either Include. (EF will give warnings saying that it ignored your Include in the console anyway).

Asynchronous LINQ

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.

C# PLINQ .AsParallel() position in query

StackOverflow,
Within C# PLINQ I understand the position of ".AsParallel()" impacts how the query is run. For example, where ".AsParallel()" occurs in the middle of a query it will execute sequentially before the method and parallel after the method. (PLINQ: Parallel Queries in .NET).
My question is, with a more complex query (below), where ".AsParallel" occurs at the start of the query (as a prefix to .Select) will all following methods execute parallel also? (currently, ".AsParallel" occurs after the .Select).
Collection =
typeof (Detail).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.SelectMany(propertyInfo => recentPhases
.Where(phase => phase.Finalised)
.SelectMany(phase => phase.PhaseDetail
.Select(keyValuePair => new
{
phase.Direction,
phase.Momentum,
keyValuePair.Key,
keyValuePair.Value
}))
.Select(arg => new
{
Key = new BmkKey
{
Direction = (arg.Direction == Dir.Up ? Dir.Up : Dir.Down),
Momentum = (arg.Momentum == Mom.Price ? Mom.Price : Mom.Time),
BarNumber = arg.Key,
DetailType = propertyInfo.Name
},
Value = (double) propertyInfo.GetValue(arg.Value, null)
}))
.AsParallel().GroupBy(grp => grp.Key)
.ToDictionary(grp => grp.Key, grp => new Distribution(grp.Select(x => x.Value)));
Yes everything will be executed parallel after the AsParallel() method is called. From msdn:
public static ParallelQuery<TSource> AsParallel<TSource>(
this IEnumerable<TSource> source)
So the input is an IEnumerable<T> and the output a ParallelQuery<T>.
If we then look at the ParallelEnumerable class:
Provides a set of methods for querying objects that implement ParallelQuery{TSource}. This is the parallel equivalent of Enumerable.
So from then on, you won't be calling the methods defined for IEnumerable<T> but you will be calling their parallel counterparts defined for ParallelEnumerable.

There is already an open DataReader associated with this Command which must be closed first - Foreach loop if statement

I am trying to loop through a list of hazards to see if a control measure has been added in ControlMeasures for each hazard.
If each hazard has a control measure then I set complete to true, if not I break the foreach loop leaving complete set to false.
I have written the foreach loop below but at runtime I get the following error showing against the if condition statement:
There is already an open DataReader associated with this Command which must be closed first.
I have done some reasearch and it would appear that I have not written the code in the correct manner but as I'm still new to this I can't work out a better and correct way of writing it.
[HttpGet]
public ViewResult AddControlMeasure(int raId)
{
// Get list of hazardids for this RA
IEnumerable<int> hazardIds = db.RiskAssessmentHazards.Where(x => x.RiskAssessmentId == raId).Select(x => x.HazardId);
var complete = false;
foreach (int HazardsId in hazardIds)
{
if (db.ControlMeasures.Where(x => x.HazardId == HazardsId && x.RiskAssessmentId == raId).Count() == 0)
{
break;
}
else
{
complete = true;
}
}
This line:
IEnumerable<int> hazardIds = db.RiskAssessmentHazards
.Where(x => x.RiskAssessmentId == raId).Select(x => x.HazardId);
Returns a lazy-evaluated sequence (really an IQueryable that will not hit the database until you begin enumerating (the foreach).
The loop:
foreach (int HazardsId in hazardIds)
{
...
}
Will open the connection to the database, get the reader, and, for each iteration, move along the reader to get the data. This means that the connection has an active reader for the duration of your loop.
Finally, this line:
if (db.ControlMeasures.Where(x => x.HazardId == HazardsId && x.RiskAssessmentId == raId).Count() == 0)
Tries to use the connection (associated with your db) to perform another query. Since you already have an active reader due to the foreach loop's queryable, this introduces the error you've received.
Notwithstanding that it's generally a bad idea to execute a query within a loop, the simplest solution would be for you to use .ToArray() or some other way to fully realize the results before iterating the loop:
int[] hazardIds = db.RiskAssessmentHazards
.Where(x => x.RiskAssessmentId == raId)
.Select(x => x.HazardId)
.ToArray();
That way the reader will be closed before you begin iterating your loop and executing the subsequent queries.
Methods like Where, Select,GroupBy and OrderBy use deferred execution (which cause open DataReader like in your case). These methods don't force the query to execute, so the query execution is deferred until enumerated. Therefore you should do enumeration yourself with ToList() or ToArray().
In your case you should force enumeration for `hazardIds' like
IEnumerable<int> hazardIds = db.RiskAssessmentHazards
.Where(x => x.RiskAssessmentId == raId)
.Select(x => x.HazardId).ToArray();

The ObjectContext instance has been disposed and can no longer be used for operations that require a connection

public List<User> Getdata()
{
using (var context =new huntableEntities())
{
IQueryable<User> userrecords = (context.Users.Where(x => x.RecuiteReferalId == 24));
userrecords.ToList().ForEach(u =>
{
u.CurrentCompany =
u.EmploymentHistories.Where(
e => e.IsCurrent && e.MasterCompany != null).Select(
e => e.MasterCompany.Description).FirstOrDefault();
u.CurrentPosition =
u.EmploymentHistories.Where(
e => e.IsCurrent && !string.IsNullOrEmpty(e.JobTitle)).
Select(e => e.JobTitle).FirstOrDefault();
});
return userrecords.AsEnumerable().ToList();
}
}
I am getting the object context disposed at the return statement
I tried by making the query and method IEnumerable but the result was the same.
I also tried by setting the lazy loading false.
Any guess where I am going wrong?
After you call ToList you are executing the query and as such disconnecting the entities from the context - You need to remove the call to ToList before your call to ForEach i.e.
userrecords.ForEach(u => ...);
Also, there is no need to call AsEnumerable as your return type isList<User>, just call ToList before you return the query i.e.
return userrecords.ToList();
Not only should this solve your problem, but it is more efficient as your now only hitting the database once.
Instead of doing the extension method, you should just use a regular Foreach loop to get the benefits of what #James is saying. Plus, you could also clean up your LINQ queries:
foreach (var u in userrecords)
{
u.CurrentCompany = u.EmploymentHistories
.FirstOrDefault(e => e.IsCurrent && e.MasterCompany != null)
.MasterCompany.Description;
u.CurrentPosition = u.EmploymentHistories
.FirstOrDefault(e => e.IsCurrent && string.IsNullOrEmpty(e.JobTitle))
.JobTitle;
}
And just have it return userrecords.ToList().
One more thing to note, though: If at any point you try to use of the navigational properties on the User records, you may also get the same exception, since the connection will have already closed, and it's trying to lazy load the entities without having a connection (and thus fails). In such a case you can either turn off lazy loading or you can just, in this method, actually make a call to that property (not to change anything, just to read it) to have it load before the connection is closed.
Not sure why the context would be disposed when you return the list, but there is a sneaky twist in your code that would yield unexpected results anyway. Your ForEach statement does not change anything! userrecords is an IQueryable and the only thing you do is enumerate it twice: in the ForEach and in the ToList() in the return statement.
You can prevent this by creating a list at the start:
List<User> userrecords = context.Users
.Include("EmploymentHistories.MasterCompany")
.Where(x => x.RecuiteReferalId == 24)
.ToList();
userrecords.ForEach(u => ...
The Include is to prevent n + 1 queries. A disposed context should not be an issue now.

Categories