.Net Core 3 EF - Order Included Array - c#

I am trying to return the newest result of Status from n array of statuses, but I have no idea how to implement this on an include.
public async Task<IEnumerable<Ticket>> GetTickets()
{
var tickets = await _context.Tickets
.Include(c => c.Client)
.Include(r => r.Region)
.Include(rl => rl.RouteLink)
.Include(e => e.Engineer)
.Include(p => p.Priority)
.Include(s => s.Statuses.).ThenInclude(s => s.Status)
.Include(tcb => tcb.TicketCreatedBy)
.Include(t => t.Team)
.ToListAsync();
return tickets;
}

Something like this would help.
var tickets = await _context.Tickets
.Include(c => c.Client)
.Include(r => r.Region)
.Include(rl => rl.RouteLink)
.Include(e => e.Engineer)
.Include(p => p.Priority)
.Include(s => s.Statuses.).ThenInclude(s => s.Status)
.Include(tcb => tcb.TicketCreatedBy)
.Include(t => t.Team)
.Select(x => new TicketDto
{
...
Statuses = x.Statuses.OrderByDescending(o => o.DateProperty).Take(1)
...
})
.ToListAsync();
but then you won't need Include directives because of the usage of Select

Related

Linq includes in nested group by query

I have a relatively complex query below with a few nested group by queries. The problem is that I don't know how I can add includes to any of the group by queries. Is there a way to include subproperties in the sub group queries in EF6?
return db.PatientOrders
.Include(x => x.Patient) // this has no effect
.Where(x => !x.ProcessedOn.HasValue && x.Patient.Home.PharmacyId == pharmacyID)
.GroupBy(x => x.Patient.Home)
.ToDictionary(x => x.Key, x => x
.ToList()
.GroupBy(y => y.Patient.Department)
.ToDictionary(y => y.Key, y => y
.Include(x => x.OrderLines) // this does not compile
.ToList()
.GroupBy(z => z.Patient)
.ToDictionary(z => z.Key, z => z.ToList(), new PatientEqualityComparer()), new HomeDepartmentEqualityComparer()), new HomeEqualityComparer());
I figured out a way to do it but I'm not sure if the solution is any good performance-wise.
// group by reshapes query so previous includes are lost
// solution: flatten after group by then do includes then group by again
return db.PatientOrders
.GroupBy(x => x.Patient.Home) // Group
.SelectMany(g => g.AsEnumerable()) // Ungroup
.Include(x => x.Patient)
.Include(x => x.Patient.Home)
.Include(x => x.Patient.Doctor)
.Include(x => x.Patient.Department)
.Include(x => x.OrderLines)
.Include(x => x.OrderLines.Select(y => y.Product))
.Where(x => !x.ProcessedOn.HasValue && x.Patient.Home.PharmacyId == pharmacyID)
.AsEnumerable() // Switch to LINQ to Objects
.GroupBy(x => x.Patient.Home) // Group again
.ToDictionary(x => x.Key, x => x
.ToList()
.GroupBy(y => y.Patient.Department)
.ToDictionary(y => y.Key, y => y
.ToList()
.GroupBy(z => z.Patient)
.ToDictionary(z => z.Key, z => z.ToList(), new PatientEqualityComparer()), new HomeDepartmentEqualityComparer()), new HomeEqualityComparer());

How to query only a small part from a navigation property (without including all data in that property)?

I have this small scenario:
var user = await dbContext.Users
.Include(u => u.Posts)
.SingleOrDefaultAsync(u => u.Id == userId);
return user
.SelectMany(u => u.Posts)
.Skip(someStartIndex)
.Take(someCount);
The problem with this scenario is that skip and take happen in the memory (after loading a lot of posts from the database into the memory because of issuing Include). I just want to query small amount of those posts from the database in the first query where I get the user (instead of including the whole posts). In other words, I want somehow to query small amount of the included data, instead of including them all.
How can I achieve that?
P.S.: In my real code the Posts are not directly under User, but in multiple sub-properties. I just omitted that to keep the code simple, as the idea of how can I include only a part should still the same.
UPDATE
My real code, to have a better understanding of the situation:
public async Task<IEnumerable<Post>> GetPostsFromFollowsAsync(Guid userId, int count, int startIndex)
{
//TODO rewrite this ugly query!!!
var user = await dbContext.Users
.Include(u => u.UserFollowing)
.ThenInclude(uf => uf.FollowingUser)
.ThenInclude(u => u.Posts)
.ThenInclude(p => p.Writer)
.ThenInclude(u => u.Profile)
.Include(u => u.UserFollowing)
.ThenInclude(uf => uf.FollowingUser)
.ThenInclude(u => u.Posts)
.ThenInclude(p => p.PostLikes)
.Include(u => u.UserFollowing)
.ThenInclude(uf => uf.FollowingUser)
.ThenInclude(u => u.Posts).
ThenInclude(p => p.PostCategories)
.ThenInclude(pc => pc.Category)
.Include(u => u.UserFollowing)
.ThenInclude(uf => uf.FollowingUser)
.ThenInclude(u => u.Posts)
.ThenInclude(p => p.Comments)
.SingleOrDefaultAsync(u => u.Id == userId);
return user
.UserFollowing
.Select(uf => uf.FollowingUser)
.SelectMany(u => u.Posts)
.Skip(startIndex)
.Take(count);
}
In this specific scenario you can start the query from Posts and use the inverse navigation property for filtering / additional includes:
var userPosts = await dbContext.Posts
.Include(p => p.User)
// other includes ...
.Where(p => p.User.Id == userId)
.Skip(someStartIndex)
.Take(someCount)
.ToListAsync();
This way the Skip / Take will happen server side.
Update: The actual structure doesn't change the concept. You just need to navigate backwards and change the user Id filter to Any due to many-to-many relationship:
return await dbContext.Posts
.Include(p => p.Writer) // Parent info
.ThenInclude(u => u.UserFollowers)
.ThenInclude(uf => uf.FollowerUser)
.Include(p => p.Writer) // Child info
.ThenInclude(u => u.Profile)
.Include(p => p.PostLikes)
.Include(p => p.PostCategories)
.ThenInclude(pc => pc.Category)
.Include(p => p.Comments)
.Where(p => p.Writer.UserFollowers.Any(uf => uf.FollowerUser.Id == userId))
.Skip(startIndex)
.Take(count)
.ToListAsync();

EFCore Linq ThenInclude Two Foreign Keys To Same Table

Does anyone see what I am doing wrong?
ProjectActivityTasks has the UnitOfMeasureId and the ProjectActivityTaskTypeId. With the way it is written, it thinks that UnitOfMeasure goes to ProjectActivityTaskType. It is erroring out on the ThenInclude for UnitOfMeasure saying
ProjectActivityTaskType does not contain a definition for UnitOfMeasure
which is correct. UnitOfMeasure goes to ProjectActivityTasks.
I was referencing this page but it does not seem to work this way: https://learn.microsoft.com/en-us/ef/core/querying/related-data
var qry = await _projectActivityRepository.GetAll()
.Include(x => x.ProjectActivityVehicles)
.ThenInclude(x => x.Vehicle)
.Include(x => x.ProjectActivityTasks)
.ThenInclude(x => x.ProjectActivityTaskType)
.ThenInclude(x => x.UnitOfMeasure)
.Where(x => x.Id == Id && x.TenantId == (int)AbpSession.TenantId)
.FirstOrDefaultAsync();
You can (and should) repeat the Include(x => x.ProjectActivityTasks) part:
var qry = await _projectActivityRepository.GetAll()
.Include(x => x.ProjectActivityVehicles)
.ThenInclude(x => x.Vehicle)
.Include(x => x.ProjectActivityTasks)
.ThenInclude(x => x.ProjectActivityTaskType)
.Include(x => x.ProjectActivityTasks)
.ThenInclude(x => x.UnitOfMeasure)
.Where(x => x.Id == Id && x.TenantId == (int)AbpSession.TenantId)
.FirstOrDefaultAsync();

NHibernate and MySql Missing column Exception

From time to time my web application begins throwing the following errors.
Using NHibernate 4.0.0.4000 and MySql.Data 6.8.3
Stack Trace
ERROR [(null)] - Message:could not execute query
NHibernate log
NHibernate.Util.ADOExceptionReporter WARN - System.IndexOutOfRangeException: Could not find specified column in results:
Once once of these errors occur it begins to happen frequently until the web application is restart.
It's odd that it only happens to some users and not all. Also I noticed in this particular log message the values of p4 and p5 should be swapped.
Is this an issue with the query cache?
Does anyone have some insight into why this is happening?
If it helps here is the gnarly query (but I see this error on much simpler queries as well)
FunderInfoViewModel funderDto = null;
Funder funderAlias = null;
Contact contactAlias = null;
var totalOpportunitiesAwardedCount = QueryOver.Of<Opportunity>()
.Where(o => o.Funder.Id == funderAlias.Id)
.And(o => o.Status == OpportunityStatus.Awarded || o.Status == OpportunityStatus.AwardedClosed)
.SelectList(list => list
.SelectCount(o => o.Id));
var totalOpportunitiesAwardedSum = QueryOver.Of<Opportunity>()
.Where(o => o.Funder.Id == funderAlias.Id)
.And(o => o.Status == OpportunityStatus.Awarded || o.Status == OpportunityStatus.AwardedClosed)
.SelectList(list => list
.SelectSum(o => o.AmountAwarded));
var totalOpportunitiesCount = QueryOver.Of<Opportunity>()
.Where(o => o.Funder.Id == funderAlias.Id)
.SelectList(list => list
.SelectCount(o => o.Id));
IEnumerable<FunderInfoViewModel> funders = _session.QueryOver(() => funderAlias)
.Left.JoinAlias(f => f.Contacts, () => contactAlias, x => x.IsDefault)
.Where(o => o.Organization.Id == organizationId)
.SelectList(list => list
.Select(x => x.Id)
.WithAlias(() => funderDto.Id)
.Select(x => x.Name)
.WithAlias(() => funderDto.Name)
.Select(x => x.Description)
.WithAlias(() => funderDto.Description)
.Select(x => x.AreasOfInterest)
.WithAlias(() => funderDto.AreasOfInterest)
.Select(x => x.Type)
.WithAlias(() => funderDto.FunderType)
.Select(x => x.TaxId)
.WithAlias(() => funderDto.TaxId)
.Select(x => x.PhoneNumber)
.WithAlias(() => funderDto.PhoneNumber)
.Select(x => x.FaxNumber)
.WithAlias(() => funderDto.FaxNumber)
.Select(x => x.EmailAddress)
.WithAlias(() => funderDto.EmailAddress)
.Select(x => x.Website)
.WithAlias(() => funderDto.Website)
.Select(x => x.CustomLink)
.WithAlias(() => funderDto.CustomLink)
.Select(x => x.MinimumFundingRange)
.WithAlias(() => funderDto.MinimumFundingRange)
.Select(x => x.MaximumFundingRange)
.WithAlias(() => funderDto.MaximumFundingRange)
.Select(() => contactAlias.FirstName)
.WithAlias(() => funderDto.PrimaryContactFirstName)
.Select(() => contactAlias.LastName)
.WithAlias(() => funderDto.PrimaryContactLastName)
.Select(() => contactAlias.Title)
.WithAlias(() => funderDto.PrimaryContactTitle)
.SelectSubQuery(totalOpportunitiesAwardedCount)
.WithAlias(() => funderDto.AwardedOpportunitiesCount)
.SelectSubQuery(totalOpportunitiesAwardedSum)
.WithAlias(() => funderDto.AwardedOpportunitiesValue)
.SelectSubQuery(totalOpportunitiesCount)
.WithAlias(() => funderDto.OpportunitiesCount)
)
.OrderBy(f => f.Name)
.Asc
.TransformUsing(Transformers.AliasToBean<FunderInfoViewModel>())
.List<FunderInfoViewModel>();
Ok got the problem. Its because some of the arguement you are passing in your prepared statement is null, thats why this error. I had similar issue earlier and i solved it by checking only if it is not null add it in query filtering.
Also there is another possibility of row lock by any other query. Are you using lock in mysql query?
2nd problem seems like see the solution stated below
https://forums.asp.net/t/1230295.aspx?IDataReader+Could+not+find+specified+column+in+results+

NHibernate QueryOver and string.format

I am working with QueryOver in NHibernate and I want to customize one property of my projected DTO using the following syntax:
IEnumerable<PersonResponseMessage> persons =
session.QueryOver<PersonEntity>()
.SelectList(list => list
.Select(p => p.Active).WithAlias(() => dto.Active)
.Select(p => p.Alert).WithAlias(() => dto.Alert)
.Select(p => p.Comments).WithAlias(() => dto.Comments)
.Select(p => string.Format("{0}api/Person/{1}", uriHelper.Root, p.Id)).WithAlias(() => dto.DetailsUrl)
)
.TransformUsing(Transformers.AliasToBean<PersonResponseMessage>())
.List<PersonResponseMessage>();
Unfortunately NHibernate cannot do this and throws an exception saying that:
Variable P referenced from scope "" is not defined
There are in common two ways. Partially we can move that concat operation on the DB side, as documented here:
16.7. Projection Functions
In this case, we'll use the Projections.Concat:
.SelectList(list => list
.Select(p => p.Active).WithAlias(() => dto.Active)
.Select(p => p.Alert).WithAlias(() => dto.Alert)
.Select(p => p.Comments).WithAlias(() => dto.Comments)
// instead of this
//.Select(p => string.Format("{0}api/Person/{1}", uriHelper.Root, p.Id))
// .WithAlias(() => dto.DetailsUrl)
// use this
.Select(p => Projections.Concat(uriHelper.Root, Projections.Concat, p.Id))
.WithAlias(() => dto.DetailsUrl)
)
.TransformUsing(Transformers.AliasToBean<PersonResponseMessage>())
.List<PersonResponseMessage>();
But I would vote for ex-post processing on the Application tier, in C#:
.SelectList(list => list
.Select(p => p.Active).WithAlias(() => dto.Active)
.Select(p => p.Alert).WithAlias(() => dto.Alert)
.Select(p => p.Comments).WithAlias(() => dto.Comments)
// just the ID
.Select(p => p.Id).WithAlias(() => dto.Id)
)
.TransformUsing(Transformers.AliasToBean<PersonResponseMessage>())
.List<PersonResponseMessage>()
// do the concat here, once the data are transformed and in memory
.Select(result =>
{
result.DetailsUrl = string.Format("{0}api/Person/{1}", uriHelper.Root, p.Id)
return result;
});

Categories