LINQ Order by with the list inside list - c#

I have an API LINQ list called examContents. Currently, it has been ordered by SectionId & DisplayOrder (no 1 & 2 as in the below picture). Now I want to add Tag Tile also as the third ordering option. Is there a way to do that in C# LINQ?
I tried adding .ThenBy(x=>x.Tags) & .ThenBy(x=>x.Tags.Title) next to DisplayOrder along with .OrderBy(t=>t.Type).ThenBy(t=>t.Title) next to Tags list. But it does not work. Can someone please help me on this?
var examContents = await this._context.ExamContents
.AsNoTracking()
.Where(x => x.IsNewVersion && versionIds.Contains(x.VersionId))
.Select(x => new ExamContentModel
{
Id = x.Id,
VersionId = x.VersionId,
LearningOutcomeId = x.LearningOutcomeId,
SectionId = x.SectionId,
Title = x.LearningOutcome.Title,
AssignedQuestionCount = x.AssignedQuestionCount,
DisplayOrder=x.DisplayOrder,
IsNewVersion=x.IsNewVersion,
Questions = x.LearningOutcome.Questions.Where(x => x.Category == QuestionCategory.Certification).Select(y => new Question
{
Title = y.Title,
Category = y.Category,
Status = y.Status,
IsDraft = y.IsDraft,
PublishedQuestionId = y.PublishedQuestionId,
ExamVersionId = y.ExamVersionId
}),
Tags = x.LearningOutcome.LearningOutcomeTags.Select(t => new Tag
{
Id = t.TagId,
Title = t.Tag.Title,
Type = t.Tag.Type,
IsRemovedFromLO = t.IsUntaggedFromLO
})
})
.OrderBy(x=>x.SectionId).ThenBy(x=>x.DisplayOrder)
.ToListAsync();
The error I am getting is:
The LINQ expression 'DbSet\r\n .Where(e => e.IsNewVersion && __versionIds_0\r\n .Contains(e.VersionId))\r\n .Join(\r\n outer: DbSet, \r\n inner: e => EF.Property<Nullable>(e, "LearningOutcomeId"), \r\n outerKeySelector: l => EF.Property<Nullable>(l, "Id"), \r\n innerKeySelector: (o, i) => new TransparentIdentifier<ExamContent, LearningOutcome>(\r\n Outer = o, \r\n Inner = i\r\n ))\r\n .OrderBy(e => e.Outer.SectionId)\r\n .ThenBy(e => e.Outer.DisplayOrder)\r\n .ThenBy(e => DbSet\r\n .Where(l0 => EF.Property<Nullable>(e.Inner, "Id") != null && EF.Property<Nullable>(e.Inner, "Id") == EF.Property<Nullable>(l0, "LearningOutcomeId"))\r\n .Join(\r\n outer: DbSet, \r\n inner: l0 => EF.Property<Nullable>(l0, "TagId"), \r\n outerKeySelector: t => EF.Property<Nullable>(t, "Id"), \r\n innerKeySelector: (o, i) => new TransparentIdentifier<LearningOutcomeTag, Tag>(\r\n Outer = o, \r\n Inner = i\r\n ))\r\n .OrderBy(l0 => l0.Inner.Type)\r\n .ThenBy(l0 => l0.Inner.Title)\r\n .Select(l0 => new Tag{ \r\n Id = l0.Outer.TagId, \r\n Title = l0.Inner.Title, \r\n Type = l0.Inner.Type, \r\n IsRemovedFromLO = l0.Outer.IsUntaggedFromLO \r\n }\r\n ))' 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.

I hope it helps:
var examContents = await this._context.ExamContents
.AsNoTracking()
.Where(x => x.IsNewVersion && versionIds.Contains(x.VersionId))
.SelectMany(examContent => examContent.LearningOutcome.learningoutcometags,
(examContent, tag) => new { examContent, tag })
.Select(examContentAndTag => new ExamContentModel
{
Id = examContentAndTag.ExamContent.Id,
VersionId = examContentAndTag.ExamContent.VersionId,
LearningOutcomeId = examContentAndTag.ExamContent.LearningOutcomeId,
SectionId = examContentAndTag.ExamContent.SectionId,
Title = examContentAndTag.ExamContent.LearningOutcome.Title,
AssignedQuestionCount = examContentAndTag.ExamContent.AssignedQuestionCount,
DisplayOrder = examContentAndTag.ExamContent.DisplayOrder,
IsNewVersion = examContentAndTag.ExamContent.IsNewVersion,
questions = examContentAndTag.ExamContent.learningoutcome.questions
.where(x => x.category == questioncategory.certification)
.select(y => new question
{
title = y.title,
category = y.category,
status = y.status,
isdraft = y.isdraft,
publishedquestionid = y.publishedquestionid,
examversionid = y.examversionid
}),
tags = examContentAndTag.tag
.select(t => new tag
{
id = t.tagid,
title = t.tag.title,
type = t.tag.type,
isremovedfromlo = t.isuntaggedfromlo
})
})
.OrderBy(examContentAndTag => examContentAndTag.ExamContent.SectionId)
.ThenBy(examContentAndTag => examContentAndTag.ExamContent.DisplayOrder)
.ThenBy(examContentAndTag => examContentAndTag.tag.type)
.ThenBy(examContentAndTag => examContentAndTag.tag.title)
.ToListAsync();
https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.selectmany?view=net-5.0

Related

Anonymous Object Breaking The Entire Method [duplicate]

This question already has an answer here:
How to select top N rows for each group in a Entity Framework GroupBy with EF 3.1
(1 answer)
Closed 6 months ago.
This is my first time using an anonymous function with LINQ syntax. We are getting multiple Id's to set up a way for us to query another table for a specific "effective date" but something is throwing off my query.
Error Message
'((Microsoft.AspNetCore.Http.DefaultHttpContext)httpContext).Session' threw an exception of type 'System.InvalidOperationException'
"System.InvalidOperationException: The LINQ expression
'GroupByShaperExpression:\nKeySelector: new { \n operatorId =
t.OperatorId, \n regionId = t.RegionId\n },
\nElementSelector:new { \n operatorId =
ProjectionBindingExpression: operatorId, \n regionId =
ProjectionBindingExpression: regionId, \n effectiveDate =
ProjectionBindingExpression: effectiveDate\n }\n
.OrderByDescending(ed => ed.effectiveDate)' 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or
'ToListAsync'.
Any help would be great!
Query
var opRegionEffectiveDate = await (from reg in _orppr.GetAllQueryable()
.Where(opReg => result.Select(o => o.Id).Contains(opReg.OperatorId)).Select(opReg => new {operatorId = opReg.OperatorId, regionId = opReg.RegionId, effectiveDate = opReg.EffectiveDate}).Distinct()
group reg by new {reg.operatorId, reg.regionId}
into regef
select new { oprId = regef.Key.operatorId, regId = regef.Key.regionId, efdate = regef.OrderByDescending(ed => ed.effectiveDate).FirstOrDefault()})
.ToListAsync();
Full Method
public async Task<IEnumerable<OperatorViewModel>> GetAllAsync()
{
var retList = new List<OperatorViewModel>();
var result = await _operatorRepository.GetAllQueryable().Include(o => o.GroupEmails).Where(o => o.TenantId == TenantId).ToListAsync();
// var regions = _orppr.GetAllQueryable();
var opRegionEffectiveDate = await (from reg in _orppr.GetAllQueryable()
.Where(opReg => result.Select(o => o.Id).Contains(opReg.OperatorId)).Select(opReg => new {operatorId = opReg.OperatorId, regionId = opReg.RegionId, effectiveDate = opReg.EffectiveDate}).Distinct()
group reg by new {reg.operatorId, reg.regionId}
into regef
select new { oprId = regef.Key.operatorId, regId = regef.Key.regionId, efdate = regef.OrderByDescending(ed => ed.effectiveDate).FirstOrDefault()})
.ToListAsync();
var regions = await _regionRepository.GetAllQueryable()
.Where(reg => opRegionEffectiveDate.Select(ore => ore.regId).Contains(reg.Id))
.ToListAsync();
foreach (var oper in result)
{
var regionsToAdd = opRegionEffectiveDate.Where(r => r.oprId == oper.Id).Select(r => r.regId);
var regionsList = regions.Where(r => regionsToAdd.Contains(r.Id));
// var currRegions = await regions.Where(opReg => opReg.OperatorId == oper.Id)
// .Include(opReg => opReg.Region)
// .Select(opReg => opReg.Region)
// .OrderBy(reg => reg.Name)
// .Distinct()
// .ToListAsync();
var currOper = _mapper.Map<Operator, OperatorViewModel>(oper);
currOper.Regions = _mapper.Map<IEnumerable<Region>, IEnumerable<RegionViewModel>>(regionsList);
foreach(var reg in currOper.Regions)
{
var item = opRegionEffectiveDate.Single(r => r.oprId == oper.Id && r.regId == reg.Id);
reg.EffectiveDate = item.efdate.ToString();
}
retList.Add(currOper);
}
return retList.OrderBy(r => r.OperatorName);
}
Try following :
var opRegionEffectiveDate = await (_orppr
.GetAllQueryable().Where(opReg => result.Select(o => o.Id).Contains(opReg.OperatorId))
.OrderByDescending(ed => ed.effectiveDate)
.Select(opReg => new {operatorId = opReg.OperatorId, regionId = opReg.RegionId, effectiveDate = opReg.EffectiveDate})
.GroupBy(reg =>new {reg.operatorId, reg.regionId})
.Select(x => x).FirstOrDefault()
.Select(x => new { oprId = x.Key.operatorId, regId = x.Key.regionId, x.effectiveDate})
.ToListAsync()
);
var opRegionEffectiveDate = await _orppr
.GetAllQueryable().Where(opReg => result.Select(o => o.Id).Contains(opReg.OperatorId))
.Distinct()
.OrderByDescending(ed => ed.effectiveDate)
.Select(opReg => new {operatorId = opReg.OperatorId, regionId = opReg.RegionId, effectiveDate = opReg.EffectiveDate})
.GroupBy(reg =>new {reg.operatorId, reg.regionId})
.Select(x => x).FirstOrDefault()
.Select(x => new { oprId = x.operatorId, regId = x.regionId, x.effectiveDate})
.ToListAsync();

How to use Query to add value in IQueryable?

I want to add a value in IQueryable get from another IQueryable here is my code
var queryReturnRequest = query.Include(x => x.Assignment)
.ThenInclude(x => x.Asset).Select(x => new ReturnRequestDto
{
AssetCode = x.Assignment.Asset.Code,
AssetName = x.Assignment.Asset.Name,
Id = x.Id,
ReturnDate = x.ReturnDate,
AssignedDate = x.Assignment.AssignedDate,
State = x.State,
UserRequestId = x.UserRequestId,
UserAcceptId = x.UserAcceptId,
RequestedBy = userList.FirstOrDefault(u => u.Id == x.UserRequestId) != null ? userList.FirstOrDefault(u => u.Id == x.UserRequestId).UserName : "",
AcceptedBy = userList.FirstOrDefault(u => u.Id == x.UserAcceptId) != null ? userList.FirstOrDefault(u => u.Id == x.UserAcceptId).UserName : "",
});
But it was crashed and return error 500, I found out that was because of userList.FirstOrDefault but when I console.log() it just work fine. So what wrong with it and is there another way to add it in IQueryable ?
I tried to user for loop to add IQueryable but it just return to null after get our of loop.
Try rewrite query in the following way. Also note that Select discards Includes, so avoid them in such case.
var queryReturnRequest = query
.Select(x => new ReturnRequestDto
{
AssetCode = x.Assignment.Asset.Code,
AssetName = x.Assignment.Asset.Name,
Id = x.Id,
ReturnDate = x.ReturnDate,
AssignedDate = x.Assignment.AssignedDate,
State = x.State,
UserRequestId = x.UserRequestId,
UserAcceptId = x.UserAcceptId,
RequestedBy = userList.Where(u => u.Id == x.UserRequestId).Select(x => x.UserName).FirstOrDefault() ?? "",
AcceptedBy = userList.Where(u => u.Id == x.UserAcceptId).Select(x => x.UserName).FirstOrDefault() ?? "",
});

How to use GroupBy() in subqueries with LINQ?

I have pretty simple LINQ expression
IQueryable<FreeBetDTO> records = UnitOfWork.FreeBets
.Include(f => f.FreeBetCategories)
.Include(f => f.FreeBetCards)
.Where(f => f.FreeBetCards.Any(cards => cards.UserId == request.UserId))
.Select(f => new FreeBetDTO
{
FreeBetId = f.FreeBetId
LineCategories = f.FreeBetCategories
.GroupBy(g => new { g.LineCategoryID, g.Title })
.Select(c =>
new LineCategoryDTO
{
LineCategoryID = c.Key.LineCategoryID,
Title = c.Key.Title
}).AsEnumerable()
});
When I am executing it I catch the error:
System.InvalidOperationException: Unable to translate collection subquery in projection since it uses 'Distinct' or 'Group By' operations and doesn't project key columns of all of it's tables which are required to generate results on client side. Missing column: t.ID. Either add column(s) to the projection or rewrite query to not use 'GroupBy'/'Distinct' operation.
at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.
....
The problem is here .GroupBy(g => new { g.LineCategoryID, g.Title }). If I don't group records, the error disappears.
I was trying a lot of cases with GroupBy() and Distinct(). But can't understand why this is happening. Because I just need grouping like this.
Error message says that you have to include Id column in projection. But you can't do that with GroupBy. So rewrite query into two steps (removed not needed includes):
var rawRecords = UnitOfWork.FreeBets
.Where(f => f.FreeBetCards.Any(cards => cards.UserId == request.UserId))
.Select(f => new
{
FreeBetId = f.FreeBetId
LineCategories = f.FreeBetCategories.Select(c => new { c.Id, c.LineCategoryID, c.Title })
.ToList()
})
.AsEnumerable();
var records = rawRecords
.Select(f => new FreeBetDTO
{
FreeBetId = f.FreeBetId
LineCategories = f.LineCategories.GroupBy(g => new { g.LineCategoryID, g.Title })
.Select(c =>
new LineCategoryDTO
{
LineCategoryID = c.Key.LineCategoryID,
Title = c.Key.Title
})
});
Similar query, but more optimal:
var query =
from f in UnitOfWork.FreeBets
from c in f.FreeBetCards
where f.FreeBetCards.Any(cards => cards.UserId == request.UserId)
select new { f.FreeBetId, c.LineCategoryID, c.Title };
query = query.Distinct();
var records = query.AsEnumerable()
.GroupBy(f => f.FreeBetId)
.Select(g => new FreeBetDTO
{
FreeBetId = g.Key
LineCategories = g.Select(c =>
new LineCategoryDTO
{
LineCategoryID = c.LineCategoryID,
Title = c.Title
})
.AsEnumerable()
});

Lambda Join Group by where clause issue

Why am I getting only one entry in DownTimeDetails list even though in Data we have 3 entries.
VehicleEventDetails Res = dbEntity.DownTimeHeaders
.Join(dbEntity.DownTimeDetails, dth => dth.DownTimeHeaderID, dtd => dtd.DownTimeHeaderID, (dth, dtd) => new { dth, dtd })
.Where(x => x.dth.DownTimeHeaderID == 42)
.GroupBy(gx => gx.dtd.DownTimeDetailID)
.Select(t => new VehicleEventDetails()
{
BookingId = t.Select(a => a.dth.BookingId).FirstOrDefault(),
DownTimeDetails = t.Select(ab => new DownTimeDetails
{
LocalDTStartTime = (DateTime)ab.dtd.LocalDTStartTime,
LocalDTEndTime = (DateTime)ab.dtd.LocalDTEndTime,
CalculatedEventDTReason = ab.dtd.CalculatedEventDTReason,
CalculatedEventDTInMinutes = (int)ab.dtd.CalculatedEventDT,
}).ToList()
}).FirstOrDefault();
You are looking for something like this:
VehicleEventDetails Res = dbEntity.DownTimeHeaders
.Where(x => x.DownTimeHeaderID == 42)
.Select(x => new VehicleEventDetails
{
BookingId = x.BookingId,
DownTimeDetails = x.DownTimeDetails
.Select(dtd=> new DownTimeDetails
{
LocalDTStartTime = (DateTime)dtd.LocalDTStartTime,
LocalDTEndTime = (DateTime)dtd.LocalDTEndTime,
CalculatedEventDTReason = dtd.CalculatedEventDTReason,
CalculatedEventDTInMinutes = (int)dtd.CalculatedEventDT,
})
.ToList()
})
.FirstOrDefault();
Notes:
Using .Join is an anti-Entity Framework pattern. Always try to use navigation properties, they exist for a reason.
Don't use .GroupBy unless you actually need a group. You don't want any grouping in this query.
As a general note, try not to make the expression variable names so confusing.

How AliasJoin in QueryOver for this sample

The Person class has a association by Identity class (one-to-one) FirstName and LastName are a property of Person class also Sex and BirthDate are a property of Identity class.
I have a sql query as the following examples:
select FirstName,LastName,Identity.Sex,Identity.BirthDate from Person_Person as Person
inner join Person_Identity as Identity on Person.Id = Identity.Person_id_fk
WHERE FirstName like '%jack%' and LastName like '%smit%'
I convert it into QueyOver.
var q = SessionInstance.QueryOver<Person>();
if (!String.IsNullOrEmpty(searchPersonDto.FirstName)) //necessary
q = q.Where(p => p.FirstName.IsLike(searchPersonDto.FirstName, MatchMode.Anywhere));
if (!String.IsNullOrEmpty(searchPersonDto.LastName)) //necessary
q = q.Where(p => p.LastName.IsLike(searchPersonDto.LastName, MatchMode.Anywhere));
Person aliasPerson = null;
q = q.SelectList(list => list
.Select(p => p.Id).WithAlias(() => aliasPerson.Id)
.Select(p => p.FirstName).WithAlias(() => aliasPerson.FirstName)
.Select(p => p.LastName).WithAlias(() => aliasPerson.LastName)
.Select(p => p.Identity.Sex).WithAlias(() => aliasPerson.Identity.Sex)
.Select(p => p.Identity.BirthDate).WithAlias(() => aliasPerson.Identity.BirthDate))
.TransformUsing(Transformers.AliasToBean<Person>());
q.List<Person>();
But join in this query is not correct. It throw a exceotion by this message :
could not resolve property: Identity.Sex of: Domain.Entities.Person
How I should join Identity by Person?
Updated : Add the similar linq query
var q = SessionInstance.Query<Person>()
.Where(p => p.FirstName == searchPersonDto.FirstName)
.Select(p => new Person(p.Id)
{
FirstName = p.FirstName,
LastName = p.LastName,
Identity = new Identity()
{
Sex = p.PersonIdentity.Sex,
BirthDate = p.Identity.BirthDate
}
}).ToList<Person>();
I need to a query by QueryOver similar to above query by Linq.
Update2: not pretty but here goes
var results = q
.JoinAlias(p => p.Identity, () => identityAlias)
.SelectList(list => list
.Select(p => p.Id)
.Select(p => p.FirstName)
.Select(p => p.LastName)
.Select(p => identityAlias.Sex)
.Select(p => identityAlias.BirthDate)
.List<object[]>()
.Select(values => new Person((int)values[0])
{
FirstName = (string)values[1],
LastName = (string)values[2],
Identity = new Identity()
{
Sex = (string)values[3],
BirthDate = (DateTime)values[4],
}
})
.ToList<Person>();
Update: from your comments i would say, this is what you need.
code to fill a PersonDto
PersonDTO aliasDTO = null;
q = q
.JoinAlias(p => p.Identity, () => identityAlias)
.SelectList(list => list
.Select(p => p.Id).WithAlias(() => aliasDTO.Id)
.Select(p => p.FirstName).WithAlias(() => aliasDTO.FirstName)
.Select(p => p.LastName).WithAlias(() => aliasDTO.LastName)
.Select(p => identityAlias.Sex).WithAlias(() => aliasDTO.Sex)
.Select(p => identityAlias.BirthDate).WithAlias(() => aliasDTO.BirthDate))
.TransformUsing(Transformers.AliasToBean<PersonDTO>())
.List<PersonDTO>();
Orginal Answer:
q.JoinAlias(p => p.Identity, () => identityAlias)
// and later
.Select(p => identityAlias.Sex)
Update: in the code posted the AliasToBeanTransformer is not needed at all
var q = SessionInstance.QueryOver<Person>();
if (!String.IsNullOrEmpty(searchPersonDto.FirstName)) //necessary
q = q.Where(p => p.FirstName.IsLike(searchPersonDto.FirstName, MatchMode.Anywhere));
if (!String.IsNullOrEmpty(searchPersonDto.LastName)) //necessary
q = q.Where(p => p.LastName.IsLike(searchPersonDto.LastName, MatchMode.Anywhere));
var results = q.Fetch(p => p.Identity).Eager
.List<Person>();

Categories

Resources