Select First of Each Navigation Property - c#

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())

Related

Get first item from each category

I have 5 categories:
Book, DVD, Rent, Buy, Sell
I use the following query to select the latest count of items.
public async Task<IList<Post>> GetRecentPostsAsync(int count)
{
return await _db.Posts
.Where(e => e.Published.HasValue && e.Published.Value <= DateTime.Today)
.OrderByDescending(e => e.Published)
.Take(count)
.ToListAsync();
}
My Post class looks like this:
public class Post
{
#region Properties
[Key]
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Title { get; set; }
[Required]
[StringLength(25)]
public string Category { get; set; }
public DateTime? Published { get; set; }
#endregion
}
This returns my count of items. I want to make a new function where I get the most recently published item from the first 4 categories, so excluding the category Sell. It should return 4 items back.
How can I achieve this?
This should give you back a List<Post> with the last published Post in each category except "Sell":
return await _db.Posts
.Where(x => x.Category != "Sell" && x.Published.HasValue && x.Published.Value <= DateTime.Today)
.GroupBy(x => x.Category)
.Select(x => x.OrderByDescending(x => x.Published).Take(1))
.SelectMany(x => x)
.ToListAsync();
Didn't immediately understand the question. So, try that.
public async Task<IList<Post>> GetRecentPostsAsync()
{
List<string> categories = new List<string> { "Book", "DVD", "Rent", "Buy" };
List<Post> result = new List<Post>();
foreach (string category in categories)
{
Post post = db.Posts.Where(e => e.Published.HasValue && e.Published.Value <= DateTime.Today && e.Category == category)
.OrderByDescending(e => e.Published)
.Take(1).First();
result.Add(post);
}
return result;
}
Try this. This will give you the count by Category excluding Sell
public class PostCategory
{
public string Category { get; set; }
public int Count { get; set; }
}
public async Task<IList<PostCategory>> GetRecentPostsAsync()
{
return await _db.Posts
.Where(e => e.Published.HasValue && e.Published.Value <= DateTime.Today && e.Category != "Sell")
.GroupBy(x => x.Category)
.Select(y => new { Category = y.Key, Count = y.Count() })
.ToListAsync();
}

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)
});

Linq2Entities query to find records given mix-max range

I have those two entities:
public class Ticket
{
public int Id { get; set; }
public int ScheduleId { get; set; }
public int SeatId { get; set; }
[DataType(DataType.Date)]
[Column(TypeName = "Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ForDate { get; set; }
public Seat Seat { get; set; }
public Schedule Schedule { get; set; }
public ICollection<TicketStop> TicketStops { get; set; }
}
public class TicketStop
{
public int Id { get; set; }
public int TicketId { get; set; }
public int LineStopStationId { get; set; }
public Ticket Ticket { get; set; }
public LineStopStation LineStopStation { get; set; }
}
public class LineStopStation
{
public int Id { get; set; }
public int LineId { get; set; }
public int StopId { get; set; }
public int Order { get; set; }
public bool IsLastStop { get; set; }
public Line Line { get; set; }
public Stop Stop { get; set; }
}
The business case is that I am implementing a bus ticket reservation system (for learning purposes mostly) and I want to find overlapping tickets.
The combination of LineId + ScheduleId + ForDate identifies uniquely that that a ticket is for certain bus at a certain date and departure time.
The problem I have is to identify, given starting and end location whether or not two tickets overlap for one or more stops.
The LineStopStation entity holds information about the StopId and the Order in which it is visited during the bus trip. So basically, overlapping tickets will have same Order number at some point (unless it is the last stop, which means that the passenger is leaving the bus).
So what I have is LineId, ScheduleId, StartId and EndId where starId and endId correspond to LineStopStation.StopId so eventually I am able to get the Order out of them like this.
int startStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == startId).Order;
int endStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == endId).Order;
So I am pretty convinced that having all this information I should be able to find if in TicketStop table I have ticket which overlaps with the data in question. TicketStop works this way - if someone bought a ticket for 3 stops I will have three records there with the same TicketId and three different LineStopStationId.
I feel that this question got a bigger than needed. So basically I have this:
public List<Seat> GetAvailableSeats(int lineId, int scheduleId, int startId, int endId, DateTime forDate)
{
int startStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == startId).Order;
int endStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == endId).Order;
var reservedSeats = _context.TicketStops
.Where(t => t.Ticket.ScheduleId == scheduleId)
.Where(t => t.Ticket.ForDate == forDate)
//basically I don't know how to proceed here.
//In pseudo code it should be something like:
.Where(t => t.Min(Order) >= endStationOrder || t.Max(Order) <= startStationOrder
}
But obs. this is not how LINQ works. So how can I find all tickets where this range overlaps?
Without an in-depth analysis of your model, perhaps something like this could give you an idea?
var reservedSeats = _context.TicketStops
.GroupBy(t => new { t.Ticket.ScheduleId, t.Ticket.ForDate })
.Where(tg => tg.Key == new { ScheduleId = scheduleId, ForDate = forDate })
.Where(tg => tg.Min(t => t.LineStopStation.Order) >= endStationOrder || tg.Max(t => t.LineStopStation.Order) <= startStationOrder);
You could also filter first and do an empty GroupBy:
var reservedSeats = _context.TicketStops
.Where(t => t.Ticket.ScheduleId == scheduleId && t.Ticket.ForDate == forDate)
.GroupBy(t => 1)
.Where(tg => tg.Min(t => t.LineStopStation.Order) >= endStationOrder || tg.Max(t => t.LineStopStation.Order) <= startStationOrder);
To return all the SeatIds, you just need to select them from the group.
var reservedSeats = _context.TicketStops
.Where(t => t.Ticket.ScheduleId == scheduleId && t.Ticket.ForDate == forDate)
.GroupBy(t => 1)
.Where(tg => tg.Min(t => t.LineStopStation.Order) >= endStationOrder || tg.Max(t => t.LineStopStation.Order) <= startStationOrder);
.SelectMany(tg => tg.Select(t => t.Ticket.SeatId));

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

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.

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