How to take X elements from a group by in C# lambda - c#

I want to take the last 4 elements from a groupby and then add it to a List. I'm using MySQL as database and asp MVC5. I tried this:
List<Payments> payments= db.Payments
.Where(e => e.Account.Active==true)
.GroupBy(e => e.IdAccount)
.Select(e => e.OrderBy(x => x.PaidDate).Take(4))
.SelectMany(e => e).ToList();
I got the following error:
Unknown column 'Join2.IdAccount' in 'where clause'
This is the Payment class:
public partial class Payment
{
public int Id { get; set; }
[ForeignKey("Account")]
[DisplayName("ID Account")]
public int IdAccount { get; set; }
[ForeignKey("Client")]
[DisplayName("ID Client")]
public int IdClient { get; set; }
public Nullable<decimal> Amount { get; set; }
[DisplayName("Paid date")]
public System.DateTime PaidDate { get; set; }
public virtual Account Account { get; set; }
public virtual Client Client { get; set; }
}

Could be that it's MySQL provider (SQL expression creator) error. But, have you tried this way?
List<Payments> payments = db.Payments
.Where(e => e.Account.Active == true)
.GroupBy(e => e.IdAccount)
.Select(e => e.OrderBy(x => x.PaidDate))
.Take(4)
.SelectMany(e => e).ToList();
If this doesn't help you, you can always pass all query to code this way:
List<Payments> payments = db.Payments
.Where(e => e.Account.Active == true)
.GroupBy(e => e.IdAccount)
.ToList() // From here all calculations will be on web server, not in SQL. So sql query will be simlier
.Select(e => e.OrderBy(x => x.PaidDate))
.Take(4)
.SelectMany(e => e).ToList();
But, it will affect perfomance.

Related

Select First of Each Navigation Property

I have three classes that look like the following. Basically, one order will be assigned multiple status.
public class Order {
public int Id { get; set; }
public DateTime Created { get; set; }
...
public ICollection<OrderStatus> OrderStatus { get; set; }
}
public class Status {
public int Id { get; set; }
public DateTime Date { get; set; }
...
public ICollection<OrderStatus> OrderStatus { get; set; }
}
public class OrderStatus {
public int Id { get; set; }
public Order Order{ get; set; }
public Status Status { get; set; }
}
My goal is to get the last status of every single order that has been assigned to a status between 0001-1000. This is what I have so far:
orders.SelectMany(x => x.OrderStatus.Where(x => x.Status.Id >= 0001 && x.Status.Id <= 1000).OrderByDescending(x => x.Date).Select(x => x.Date));
This part is where I only want to select the first of each and not all of them.
.Select(x => x.Date)
Edit:
I got it working, with the help of #StriplingWarrior!
I forgot to mention, that I only want the date of the last status per order. So basically, I will have a list of dates at the end for each order. This is the final product:
List<DateTime> dates = buchungen.SelectMany(x => x.OrderStatus.Where(x => x.Status.Id >= 0001 && x.Status.Id <= 1000).GroupBy(x => x.OrderId).Select(g => g.OrderByDescending(x => x.Date).FirstOrDefault()).Select(x => x.Date));
If you want the order information along with its last status, this is the general structure to use:
orders
.Select(x => new
{
order = x,
lastStatus = x.OrderStatus
.Where(x => x.Status.Id >= 0001 && x.Status.Id <= 1000)
.OrderByDescending(x => x.Date)
.FirstOrDefault()
})
If you literally only want the last status per order, you can probably go straight to the order status table, something like this:
context.OrderStatuses
.Where(s => s.Id >= 0001 && s.Id <= 1000)
.GroupBy(s => s.OrderId)
.Select(g => g.OrderByDescending(x => x.Date).FirstOrDefault())

Converting SQL query to LINQ or LINQ fluent Syntax

I have SQL query like this
SELECT T.*
FROM
(
SELECT ServiceRecords.DistrictId, Districts.Name as DistrictName, COUNT(Distinct(NsepServiceRecords.ClientRegNo)) AS ClientsServedCount
FROM ServiceRecords
INNER JOIN Districts ON ServiceRecords.DistrictId = Districts.ID
INNER JOIN NsepServiceRecords ON NsepServiceRecords.ServiceRecordId = ServiceRecords.Id
WHERE ServiceRecords.CreatedAtUtc >= #StartDate
AND ServiceRecords.CreatedAtUtc <= #EndDate
AND ServiceRecords.DistrictId = #DistrictId
GROUP BY ServiceRecords.DistrictId, Districts.Name
) AS T
ORDER BY T.DistrictName ASC, T.DistrictId
Query results:
DistrictId DistrictName ClientsServedCount
8d059005-1e6b-44ad-bc2c-0b3264fb4567 Bahawalpur 117
27ab6e24-50a6-4722-8115-dc31cd3127fa Gujrat 492
14b648f3-4912-450e-81f9-bf630a3dfc72 Jhelum 214
8c602b99-3308-45b5-808b-3375d61fdca0 Lodhran 23
059ffbea-7787-43e8-bd97-cab7cb77f6f6 Muzafarghar 22
580ee42b-3516-4546-841c-0bd8cef04df9 Peshawar 211
I'm struggling converting this to LINQ to entities query. I want to get same results (except District Id column) using LINQ.
I have tried like this, but not working as expected. Can somebody tell me what I'm doing wrong?
_dbContext.ServiceRecords
.Include(x => x.District)
.Include(x=>x.NsepServiceRecords)
.GroupBy(x => x.DistrictId)
.Select(x => new DistrictClientsLookUpModel
{
DistrictName = x.Select(record => record.District.Name).FirstOrDefault(),
ClientsServedCount = x.Sum(t=> t.NsepServiceRecords.Count)
});
Model classes are like this
public class BaseEntity
{
public Guid Id { get; set; }
}
public class NsepServiceRecord : BaseEntity
{
public DateTime CreatedAtUtc { get; set; }
public Guid ServiceRecordId { get; set; }
public string ClientRegNo { get; set; }
// other prop .......
public virtual ServiceRecord ServiceRecord { get; set; }
}
public class ServiceRecord : BaseEntity
{
public DateTime CreatedAtUtc { get; set; }
public string DistrictId { get; set; }
public virtual District District { get; set; }
public virtual ICollection<NsepServiceRecord> NsepServiceRecords { get; set; }
}
public class DistrictClientsLookUpModel
{
public string DistrictName { get; set; }
public int ClientsServedCount { get; set; }
}
I'm using Microsoft.EntityFrameworkCore, Version 2.2.4
EDIT
I have also tried like this
var startUniversalTime = DateTime.SpecifyKind(request.StartDate, DateTimeKind.Utc);
var endUniversalTime = DateTime.SpecifyKind(request.EndDate, DateTimeKind.Utc);
return _dbContext.NsepServiceRecords
.Join(_dbContext.ServiceRecords, s => s.ServiceRecordId,
r => r.Id, (s, r) => r)
.Include(i => i.District)
.Where(x => x.DistrictId == request.DistrictId
&& x.CreatedAtUtc.Date >= startUniversalTime
&& x.CreatedAtUtc.Date <= endUniversalTime)
.OrderBy(x => x.DistrictId)
.GroupBy(result => result.DistrictId)
.Select(r => new DistrictClientsLookUpModel
{
DistrictName = r.Select(x=>x.District.Name).FirstOrDefault(),
ClientsServedCount = r.Sum(x=>x.NsepServiceRecords.Count())
});
Another try,
from s in _dbContext.ServiceRecords
join record in _dbContext.NsepServiceRecords on s.Id equals record.ServiceRecordId
join district in _dbContext.Districts on s.DistrictId equals district.Id
group s by new
{
s.DistrictId,
s.District.Name
}
into grp
select new DistrictClientsLookUpModel
{
DistrictName = grp.Key.Name,
ClientsServedCount = grp.Sum(x => x.NsepServiceRecords.Count)
};
It takes too long, I waited for two minutes before I killed the request.
UPDATE
EF core have issues translating GroupBy queries to server side
Assuming the District has a collection navigation property to ServiceRecord as it should, e.g. something like
public virtual ICollection<ServiceRecord> ServiceRecords { get; set; }
you can avoid the GroupBy by simply starting the query from District and use simple projection Select following the navigations:
var query = _dbContext.Districts
.Select(d => new DistrictClientsLookUpModel
{
DistrictName = d.Name,
ClientsServedCount = d.ServiceRecords
.Where(s => s.CreatedAtUtc >= startUniversalTime && s.CreatedAtUtc <= endUniversalTime)
.SelectMany(s => s.NsepServiceRecords)
.Select(r => r.ClientRegNo).Distinct().Count()
});
You don't appear to be doing a join properly.
Have a look at this:
Join/Where with LINQ and Lambda
Here is a start on the linq query, I'm not sure if this will give you quite what you want, but its a good start.
Basically within the .Join method you need to first supply the entity that will be joined. Then you need to decide on what they will be joined on, in this case district=> district.Id, serviceRecord=> serviceRecord.Id.
_dbContext.ServiceRecords
.Join( _dbContext.District,district=> district.Id, serviceRecord=> serviceRecord.Id)
.Join(_dbContext.NsepServiceRecords, Nsep=> Nsep.ServiceRecord.Id,district=>district.Id)
.GroupBy(x => x.DistrictId)
.Select(x => new DistrictClientsLookUpModel
{
DistrictName = x.Select(record => record.District.Name).FirstOrDefault(),
ClientsServedCount = x.Sum(t=> t.NsepServiceRecords.Count)
});

Filtering "include" entities in EF Core

I have the following models
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class RoleInDuty
{
public int roleInDutyId { get; set; }
public string Name { get; set; }
public int typeOfDutyId { get; set; }
public TypeOfDuty typeOfDuty { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class PersonRole
{
public int PersonId { get; set; }
public Person Person { get; set; }
public int RoleInDutyId { get; set; }
public RoleInDuty RoleInDuty { get; set; }
}
And now I can load all people with all their roles using the following code:
var people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e => e.RoleInDuty).ToList();
But I wantn't load all data to List, I need load PersonRole according entered typeOfDutyId.
I am trying to solve this with the following code
people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id)).ToList();
But VS throw error
InvalidOperationException: The Include property lambda expression 'p
=> {from PersonRole t in p.PersonRoles where ([t].RoleInDuty.typeOfDutyId == __typeOfDuty_typeOfDutyId_0) select
[t]}' is invalid. The expression should represent a property access:
't => t.MyProperty'. To target navigations declared on derived types,
specify an explicitly typed lambda parameter of the target type, E.g.
'(Derived d) => d.MyProperty'. For more information on including
related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
As I understand I can't access property RoleInDuty.typeOfDutyId because i'm not include it yet.
I solved this problem with the following code
people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e=>e.RoleInDuty).ToList();
foreach (Person p in people)
{
p.PersonRoles = p.PersonRoles
.Where(e => e.RoleInDuty.typeOfDutyId == Id)
.ToList();
}
Finally, filter in Include with ef core 5. Details in MSDN doc: https://learn.microsoft.com/en-us/ef/core/querying/related-data#filtered-include
Var people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id))
.ToList();
var blogs = context.Blogs
.Include(e => e.Posts.OrderByDescending(post => post.Title).Take(5)))
.ToList();
devnull show the next How to filter "Include" entities in entity framework?, and there the same problem, I read it, and find the answer. Solve my problem can with the next:
var temp = _context.Persons.Select(s => new
{
Person = s,
PersonRoles= s.PersonRoles
.Where(p => p.RoleInDuty.typeOfDutyId == this.typeOfDuty.typeOfDutyId)
.ToList()
}).ToList();

Why does calling .Include() after a .Join() not work?

I have a DB structure which is not ideal, but I have coded it into EF like this:
[Table("Item")]
public class Item
{
[Key] public int Id { get; set; }
public int CategoryId { get; set; }
public int ItemTypeId { get; set; } // An ItemTypeId of 1 means this row refers to an Article
public int ItemId { get; set; } // this refers to the Article primary key
}
[Table("Article")]
public class Article
{
[Key] public int Id { get; set; }
...
public virtual ICollection<SubArticle> SubArticles { get; set; }
}
[Table("SubArticle")]
public class SubArticle
{
...
public int ArticleId { get; set; }
}
modelBuilder.Entity<Article>().Collection(_ => _.SubArticles).InverseReference(_ => _.Article).ForeignKey(_ => _.ArticleId);
What I want to do is get all articles (with the corresponding sub-articles) that belong to a specific category. I have this query which is working:
var result = await Context.Set<Item>()
.Where(i => i.CategoryId == 200)
.Where(i => i.ItemTypeId == 1)
.Join(
Context.Set<Article>().Include(a => a.SubArticles),
i => i.ItemId,
a => a.Id,
(i,a) => a)
.ToListAsync();
result.SubArticles.First(); // works
My question is why does this query not work:
var result = await Context.Set<Item>()
.Where(i => i.CategoryId == 200)
.Where(i => i.ItemTypeId == 1)
.Join(
Context.Set<Article>(),
i => i.ItemId,
a => a.Id,
(i,a) => a)
.Include(a => a.SubArticles)
.ToListAsync();
result.SubArticles.First(); // error: result.SubArticles is null

Order by property of nested object with NHibernate Linq

I have class Request with list of Comments. Each Request can have zero, one or many Comments.
public class Request
{
public virtual string Id
{ get; protected set; }
public virtual DateTime Date
{ get; set; }
public virtual byte RequestStatusId
{ get; set; }
public virtual Payment Payment
{ get; set; }
public virtual IList<RequestComment> RequestComments
{ get; set; }
}
public class RequestComment
{
public virtual int Id
{ get; protected set; }
public virtual DateTime Date
{ get; set; }
public virtual string CommentText
{ get; set; }
public virtual Request Request
{ get; set; }
public virtual User User
{ get; set; }
}
I'm using NHibernate.Linq to get data from database.
When I sort, for example by Request Id, it looks like this:
var query = _session.Query<Request>()
.Where(r => r.RequestStatusId == requestStatusId)
.OrderBy(r => r.Id)
.Skip(pageNo * pageSize)
.Take(pageSize);
return query.ToFuture().AsQueryable();
When I need last comment for Request I get it like this:
public RequestComment GetLastCommentForRequest(string requestId)
{
var query = _session.Query<RequestComment>()
.Where(r => r.Request.Id == requestId)
.OrderByDescending(r => r.Date)
.FirstOrDefault();
return query;
}
Now I need to get Requests, with their last comment (if exists) and all sorted by CommentText. I was trying to do order in Request query with:
.OrderBy(x => x.RequestComments.Where(y => y.Request.Id == x.Id).OrderByDescending(y => y.Date).FirstOrDefault())
But it's not working and I'm getting error »Exception has been thrown by the target of an invocation.«
UPDATE
This is ok, but it's not sorted by last comment, but by first found:
.OrderBy(r => r.RequestComments.Select(x => x.CommentText).FirstOrDefault())
How about this :
.OrderBy(x => x.RequestComments
.OrderByDescending(y => y.Date)
.Select(x => x.CommentText)
.FirstOrDefault()
)
UPDATE :
It seems that linq above translated to query with subquery having order by clause which is not allowed. Try this instead :
.OrderBy(x => x.RequestComments
.Where(y => y.Date == x.RequestComments.Max(o => o.Date))
.Select(u => u.CommentText)
.FirstOrDefault()
)
You could try:
.OrderBy(x => x.RequestComments.Select(y => y.Date).DefaultIfEmpty().Max())

Categories