LINQ: IAsyncGrouping and IGrouping conflict (2) - c#

THIS ISSUE IS A DUPLICATE OF
LINQ: IAsyncGrouping and IGrouping conflict
... which has been marked as a duplicate of an unrelated question.
I have this code:
var result = await _issueRepository.List(l => true)
.Include(l => l.Issue)
.Where(l => l.Issue.Deleted == false
&& linkedIncidentsTotal.Any(x => x.Contains(l.Lable.LabelTitle)))
.GroupBy(l => l.Lable)
.ToListAsync();
And when it executes, i am getting error:
Expression of type 'System.Collections.Generic.IAsyncEnumerable1[System.Linq.IAsyncGrouping2[mobo.Models.Lables,mobo.Models.LableIssues]]' cannot be used for return type 'System.Collections.Generic.IAsyncEnumerable1[System.Linq.IGrouping2[mb.Models.Lables,mb.Models.LableIssues]]'

This is a bug in EF Core https://github.com/aspnet/EntityFrameworkCore/issues/10716 which is fixed in 2.1.

Related

Linq Where clause to generate List based on results of a different List

I am trying to generate a List<Object> using Where clause with properties from a different List<Object>. I know that I could use a .Include(), similar to a SQL join if I were using Entity Framework but I am not using Entity Framework so I don't think it would work. I have:
List<Problem> problems = MethodToCallDbAndGenerateList(); //ado.net
problems = problems.Where(x => x.Property1 == "value").ToList();
//remaining logic
List<Solved> solved = MethodToCallDb()
.Where(x => x.SolvedId == problems.ProblemId)
.ToList();
//error happens in Where(...problems.ProblemId);
//List<Problem> does not contain a definition for ProblemId
The error says the List<Problem> does not contain ProblemId but I do have that property in my class. So I am unsure of why I am getting that error.
How can I generate my List<Solved> based on filtered results from
.Where(x => x.SolvedId == problems.SolvedId);
Using LINQ to Objects, you can use the Enumerable.Join method to create a join between two List<T>s and just return the matching members:
List<Problem> problems = MethodToCallDbAndGenerateList()
.Where(x => x.Property1 == "value")
.ToList();
List<Solved> solved = MethodToCallDb()
.Join(problems, s => s.SolvedId, p => p.ProblemId, (s,p) => s)
.ToList();
However, if there are a lot of problems and solved, or if you frequently check the same list of problems, or if you are only creating problems to use in the join, you'd be better off creating a HashSet:
var problemIDs = problems.Select(p => p.ProblemId).ToHashSet();
List<Solved> solved = MethodToCallDb()
.Where(s => problemIDs.Contains(s.SolvedId))
.ToList();
NOTE: If you are only creating problems to use in the join, better to skip creating the List<Problem> and just do:
var problemIDs = MethodToCallDbAndGenerateList()
.Where(x => x.Property1 == "value")
.Select(p => p.ProblemId)
.ToHashSet();

EF Core 3.1 Client Side Evaluation Issue With Boolean Async Call

I am migrating form .Net 2.1 to 3.1 and this is including EF Core upgrade.
Now I had LINQ query as following, that worked with no issues:
var application = await _db.CustomerApplications
.AsNoTracking()
.Include(i => i.CustomerApplicationFields)
.Include(i => i.Customer)
.Where(x => x.Customer.PublicId == formId && x.IsPublished) // Also tried with &
.OrderByDescending(o => o.Version)
.FirstOrDefaultAsync();
With EF Core 3.1 I get error:
The LINQ expression 'DbSet<CustomerApplication>
.Where(c => !(c.Deleted))
.Join(
outer: DbSet<Customer>
.Where(c0 => !(c0.Deleted)),
inner: c => EF.Property<Nullable<long>>(c, "CustomerId"),
outerKeySelector: c0 => EF.Property<Nullable<long>>(c0, "Id"),
innerKeySelector: (o, i) => new TransparentIdentifier<CustomerApplication, Customer>(
Outer = o,
Inner = i
))
.Where(c => c.Inner.PublicId == __formId_0 && c.Outer.IsPublished)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
When I convert this query as following, then it works (moving bool evaluation outside):
var application = await _db.CustomerApplications
.AsNoTracking()
.Include(i => i.CustomerApplicationFields)
.Include(i => i.Customer)
.Where(x => x.Customer.PublicId == formId)
.OrderByDescending(o => o.Version)
.ToListAsync();
var result = application.FirstOrDefault(x => x.IsPublished);
Could someone explain to me, why this is an issue? I also tried x.IsPublished == true, which had no effect. This seems to be quite random.
I also tried with AsTracking().
Before EF Core 3.0, queries that could not be translated into SQL queries were evaluated on the client side. This behaviour was dismissed and an exception is thrown instead of evaluating non-translatable queries on the client side, now.
Also I think the new behaviour should not lead to any big performance issues when you write var result = application.FirstOrDefault(x => x.IsPublished); separately, because the same thing happened before. It was just not visible before. (Please correct me if this assumption is wrong!)
Also you can try following if you want to have one query (did not test this):
var application = await _db.CustomerApplications
.AsNoTracking()
.Include(i => i.CustomerApplicationFields)
.Include(i => i.Customer)
.Where(x => x.Customer.PublicId == formId)
.OrderByDescending(o => o.Version)
.FirstOrDefaultAsync(x => x.IsPublished);
You can read about it in detail here: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client
You should use && instead of & or you can add another where clause.

EF Core 3 Linq could not be translated

I tried to build a query in ef core 3.0 that gets the full process from the db server
IEnumerable<int> stIds = stateIds;
var rtables = await db.Order.
Join(db.OrderDetail, order => order.OrderId, orderdetail => orderdetail.OrderId, (order, orderdetail) => new { order, orderdetail }).
Where(x => x.order.SellerId == sellerId && stIds.Contains(x.orderdetail.OrderStateId)&&x.order.RtableId != null)
.GroupBy(x =>
x.order.RtableId
)
.Select(x => new RtableState { RtableId = x.Key ?? 0, OrderStateId = x.OrderByDescending(x => x.orderdetail.OrderStateId).Select(x => x.orderdetail.OrderStateId).FirstOrDefault() }).ToListAsync();
I get this error:
{
"Message": "Processing of the LINQ expression 'AsQueryable<<>f__AnonymousType52>(OrderByDescending<<>f__AnonymousType52, int>(\r\n source: NavigationTreeExpression\r\n Value: default(IGrouping, <>f__AnonymousType52>)\r\n Expression: (Unhandled parameter: e), \r\n keySelector: (x) => x.orderdetail.OrderStateId))' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.",
"Inner": ""
}
I know the query is too complex for EF Core 3.0, but is this a bug or should it not work?
My solution is to split the request.
IEnumerable<int> stIds = stateIds;
var rtableStatesServer = await db.Order.
Join(db.OrderDetail, order => order.OrderId, orderdetail => orderdetail.OrderId, (order, orderdetail) => new { order, orderdetail }).
Where(x => x.order.SellerId == sellerId && stIds.Contains(x.orderdetail.OrderStateId) && x.order.RtableId != null)
.GroupBy(x => new RtableState
{
RtableId =
x.order.RtableId ?? 0,
OrderStateId = x.orderdetail.OrderStateId
})
.Select(x => new RtableState { RtableId = x.Key.RtableId, OrderStateId = x.Key.OrderStateId }).ToListAsync();
var rtableStates = rtableStatesServer.GroupBy(r => r.RtableId,
(key, value) => new RtableState
{
RtableId = key,
OrderStateId = value.OrderByDescending(x=>x.OrderStateId).Select(x => x.OrderStateId).FirstOrDefault()
}).ToList();
As indicated in the exception message, the problem is caused by the expression
x.OrderByDescending(y => y.orderdetail.OrderStateId)
.Select(y => y.orderdetail.OrderStateId)
.FirstOrDefault()
where x is IGrouping<,> produced by GroupBy operator.
This may indicate either a bug or a limitation in EF Core.
I would consider it a limitation, which might never be fixed because GroupBy result containing expressions other than key and aggregate expressions have no natural SQL equivalent.
The general solution is to avoid GroupBy where possible and use alternative constructs with correlated subqueries. But this particular query has simple natural solution because the expression
set.OrderByDescending(item => item.Property).Select(item => itm.Property).FirstOfDefault()
can be expressed with
set.Max(item => item.Property)
which is a standard (thus supported aggregate).
Replace the aforementioned problematic expression with
x.Max(y => y.orderdetail.OrderStateId)
and the problem will be solved.

Return an object in C#

I have a method that gets a list of people from the database using aspnet boilerplate framework. Since adding the include line I get the error message:
Specified cast is not valid
Here is the full error message:
Specified cast is not valid
Here is the c# method:
public ListResultDto<PersonListDto> GetPeople(GetPeopleInput input)
{
var persons = _personRepository
.GetAll()
.Include(p => p.Phones)
.WhereIf(
!input.Filter.IsNullOrEmpty(),
p => p.Name.Contains(input.Filter) ||
p.Surname.Contains(input.Filter) ||
p.EmailAddress.Contains(input.Filter)
)
.OrderBy(p => p.Name)
.ThenBy(p => p.Surname)
.ToList();
return new ListResultDto<PersonListDto>(ObjectMapper.Map<List<PersonListDto>>(persons));
}
Any ideas on what I need to do to resolve the issue?
I think its cannot be break on .Include(p => p.Phones) line. Because there is nothing about casting. You considered about constructor of the ListResultDto class can take ObjectMapper.Map<List<PersonListDto>> as parameter. Also maybe you need to change that line with ObjectMapper.Map<ListResultDto<PersonListDto>>

Entity Framework Take() returns more elements [duplicate]

This question already has answers here:
Entity Framework Include OrderBy random generates duplicate data
(6 answers)
Closed 6 years ago.
Can someone please explain why following query returns list of 8 vessels?
var vessels = await db.Vessels
.Include(m => m.Images.Select(c => c.Activity))
.Include(m => m.VesselAddresses.Select(c => c.Address))
.Where(m => m.Images.Any(c => c.Activity.Active))
.Where(m => m.Activity.Active)
.Where(m => m.Listed)
.Where(m => m.Activity.User.Active)
.OrderBy(m => Guid.NewGuid())
.Take(4)
.ToListAsync();
If i remove Include(m => m.VesselAddresses.Select(c => c.Address)) or OrderBy from the query, then it works just fine and returns 4 records, but if i leave it as it is, then it returns 8 records, even i specified Take(4)
EDIT
This is almost the same query for apartments table, but this query works just fine and always returns 4 rows:
var apartments = await db.Apartments
.Include(m => m.Images.Select(c => c.Activity))
.Include(m => m.Address)
.Where(m => m.Images.Any(c => c.Activity.Active))
.Where(m => m.Activity.Active)
.Where(m => m.Listed)
.Where(m => m.Activity.User.Active).OrderBy(m => Guid.NewGuid())
.Take(4)
.ToListAsync();
Entity Framework doesn't run the query you are making until you call the ToListAsync, hence my guess would be that you Include can't be translated into SQL so its being ignored by the query builder until after it executes the SQL which because take converts into sql as TOP, means that the include is being applied after Take
moving the .Take(4) after the .ToListAsync() should correct
also i assumed you are using .OrderBy(m => Guid.NewGuid()) to randomise the results i would suggest instead Random.NextDouble() guid is overkill for randomisation

Categories