question on linq select join - linq

I would like to list out all the students who have yet to pay off their course fee
i.e. when a student is clicked, list out all pending fees by months.
This is what I have done so far..
These are all the students active courses.
Below are the payments record paid by student to their course.
Now i would like to list out all the pending payment foreach students
e.g. last payment to the course is on 2/11/2011. If the datetime now is May, Then i would like to show the pending amount as
Month Amount
March 100
April 200
May 400
This is what I tried..
foreach (var result in activeCourses)
{
//Show all the pending paid coures from dateJoin till datetime.Now
DateTime JoinedDate = Convert.ToDateTime(result.cus.Date);
for (DateTime d = JoinedDate; d.Month <= DateTime.Now.Month; d = d.AddMonths(1))
{
Payment payment = new Payment();
payment.Course.Id = result.c.Id;
payment.Course.Name = result.c.Name;
payment.Course.Fee = result.c.Fee;
payment.CourseByStudent.CourseUserStatus.Date = result.cu.Date;
payment.CourseByTutor.TutorId = result.t.TutorId;
payment.User.Name = result.u.Name;
payment.MonthId = d.Month;
payment.MonthToPay = d.ToString("MMM");
output.Add(payment);
}
}
The logic given above does not seem to be efficient in case the student does not pay anything for his courses, then I have to check the pending payment since his first JoinedDate. OtherWise I need to check the pending payment from the DateIssue in the Payment table according to that particular course..please advice thanks

i will use back the same approach to show the late payment.
for (DateTime d = lastPaidDate.AddMonths(1); d.Month <= DateTime.Now.Month; d = d.AddMonths(1))
Thanks

Related

How to make zero counts show in LINQ query when getting daily counts?

I have a database table with a datetime column and I simply want to count how many records per day going back 3 months. I am currently using this query:
var minDate = DateTime.Now.AddMonths(-3);
var stats = from t in TestStats
where t.Date > minDate
group t by EntityFunctions.TruncateTime(t.Date) into g
orderby g.Key
select new
{
date = g.Key,
count = g.Count()
};
That works fine, but the problem is that if there are no records for a day then that day is not in the results at all. For example:
3/21/2008 = 5
3/22/2008 = 2
3/24/2008 = 7
In that short example I want to make 3/23/2008 = 0. In the real query all zeros should show between 3 months ago and today.
Fabricating missing data is not straightforward in SQL. I would recommend getting the data that is in SQL, then joining it to an in-memory list of all relevant dates:
var stats = (from t in TestStats
where t.Date > minDate
group t by EntityFunctions.TruncateTime(t.Date) into g
orderby g.Key
select new
{
date = g.Key,
count = g.Count()
}).ToList(); // hydrate so we only query the DB once
var firstDate = stats.Min(s => s.date);
var lastDate = stats.Max(s => s.date);
var allDates = Enumerable.Range(1,(lastDate - firstDate).Days)
.Select(i => firstDate.AddDays(i-1));
stats = (from d in allDates
join s in stats
on d equals s.date into dates
from ds in dates.DefaultIfEmpty()
select new {
date = d,
count = ds == null ? 0 : ds.count
}).ToList();
You could also get a list of dates not in the data and concatenate them.
I agree with #D Stanley's answer but want to throw an additional consideration into the mix. What are you doing with this data? Is it getting processed by the caller? Is it rendered in a UI? Is it getting transferred over a network?
Consider the size of the data. Why do you need to have the gaps filled in? If it is known to be returning over a network for instance, I'd advise against filling in the gaps. All you're doing is increasing the data size. This has to be serialised, transferred, then deserialised.
If you are going to loop the data to render in a UI, then why do you need the gaps? Why not implement the loop from min date to max date (like D Stanley's join) then place a default when no value is found.
If you ARE transferring over a network and you still NEED a single collection, consider applying D Stanley's resolution on the other side of the wire.
Just things to consider...

Left join with grouping and ordering

Musicians write songs. Songs are played on the air.
I have database tables Musicians, Songs and AirTimes. The AirTimes table entries hold information on which song was played on which date and for how many minutes.
I have classes Musician, Song, AirTime that correspond to the tables. The classes have navigational properties that point to the other entity. Arrows below represent navigation.
Musician <--> Song <--> AirTime
From the database, I have to retrieve all the Musicians and dates on which his/her song got AirTime. Plus I want to show the number of Songs played on a particular date and the number of minutes played on that date.
In Microsoft SQL, I would do it as follows:
select
dbo.Musicians.LastName
, dbo.AirTimes.PlayDate
, count(dbo.AirTimes.PlayDate) as 'No. of entries'
, sum(dbo.AirTimes.Duration) as 'No. of minutes'
from dbo.Musicians
left outer join dbo.Songs
on dbo.Musicinas.MusicianId = dbo.Songs.MusicianId
left outer join dbo.AirTimes
on dbo.Songs.SongId = dbo.AirTimes.SongId
and '2014-07-01T00:00:00' <= dbo.AirTimes.PlayDate
and dbo.AirTimes.PlayDate <= '2014-07-31T00:00:00'
group by
dbo.Musicians.LastName
, dbo.AirTimes.PlayDate
order by
dbo.Musicians.LastName
, dbo.AirTimes.PlayDate
Can anybody “translate” this into linq-to-entitese?
Update Aug. 9, 2012
I'm unable to confirm grudolf's schemes do what I wanted. I accomplished things with a different technique. Nonetheless, I accept his/her answer.
As you have the navigational properties in both directions you can start either from AirTimes:
var grpTime = (
from a in AirTimes
where a.Date >= firstDate && a.Date < lastDate
group a by new {a.Song.Musician.LastName, a.Song.Title, a.Date} into grp
select new {
grp.Key.LastName,
grp.Key.Title,
grp.Key.Date,
Plays = grp.Count(),
Seconds = grp.Sum(x => x.Duration)
}
);
or from Musicians:
var grpMus = (
from m in Musicians
from s in m.Songs
from p in s.Plays
where p.Date >= firstDate && p.Date < lastDate
group p by new {m.LastName, s.Title, p.Date} into grp
select new {
grp.Key.LastName,
grp.Key.Title,
grp.Key.Date,
Plays = grp.Count(),
Seconds= grp.Sum(x => x.Duration)
}
);
EDIT:
To display all musicians, including those without airtime you can use another level of grouping - in first step you calculate totals per song+day and then group them with song's author. It could probably work directly with database but I didn't manage to find an efficient way to do it. Yet. ;) With code, the original AirTimes result is changed to return Musician instead of his lastname and then joined to list of all musicians:
//Airtimes for musicians
var grpAir = (
from a in AirTimes
where a.Date >= firstDate && a.Date < lastDate
group a by new {a.Song.Musician, a.Date} into grp
select new {
//Musician instead of his LastName for joining. Id would work too
grp.Key.Musician,
//grp.Key.Musician.LastName,
Date=grp.Key.Date,
Plays = grp.Count(),
Secs = grp.Sum(x => x.Duration)
}
);
var res = (
from m in Musicians
join g in grpAir on m equals g.Musician into g2
from g in g2.DefaultIfEmpty()
orderby m.LastName
select new {
m.LastName,
Date = (g==null ? null : g.Date),
Plays = (g==null ? 0 : g.Plays),
Secs = (g==null ? 0 : g.Secs)
}
);
You can find a more complete LINQPad sample at https://gist.github.com/3236238

Entity Framework Take N items of child collection

Say I have a Customer entity, and a Sales entity, of 1-to-many relationship.
How could I get all Customers with N number of most recent sales?
var result = Customers.Where(c => c.Sales.Any());
This would return all customers with ALL their sales.
What if I want just 2 sales record from each customer?
P/S: I can do that with query syntax, i'm looking for method syntax solution. I just can't figure out how to chain them together in method syntax form
var result = from cust in context.Customers
select new
{
Customers = cust,
Sales = cust.Sales.OrderBy(s => s.Date).Take(2)
};
This works, but i'm not sure if this is the best way to do it.
EDIT:
OK, it turns out the query syntax that i included here is not working too.
Only the Sales in the anonymous type is effectively reduced to 2 records.
var filtered = result.AsEnumerable().Select(r => r.Customers);
doing this will still result in a list of customers with ALL their sales
You can do a project as described in here
var dbquery = Customers.Select( c => new {
Customer = c,
Sales = c.Sales.OrderBy(s => s.Date)
.Take(2).Select( s => new { s, s.SalesDetails})
});
var customers = dbquery
.AsEnumerable()
.Select(c => c.Customer);

Nested Query or Sub query linq

I have two classes. Bills and Transactions. One bill is made up of many transactions. I am able to display bills and I am able to display transactions on their own. But I would like to display the last 10 bills (this part is done), but each bill should show all its transactions.
This part of the code is used to get all transactions of a bill
{ Bill bill = (Bill)Bills.Instance.GetBillsByCustomerID(id);
//get all transactions of bill
var transactions = from t in this._entities.Transactions
where t.Bill.bID == bill.bID
select new
{
t.Product.pName, t.tQty, t.tUnitPrice, t.Bill.bTotal, t.Bill.bTimestamp, t.Bill.bCustomerIDF
};
}
Now I would like that the following query below, would have some sort of nested query where all transactions OF EACH BILL are obtained: (at the moment, this only displays 10 bills - and no transactions
{
//returns top 10
var bills = (from b in this._entities.Bills
where b.bCustomerIDF == id
orderby b.bTimestamp descending
select new { b.bTotal, b.bTimestamp, b.Customer.cName}).Take(10);
return bills;
}
Can you please guide me to a simple solution? Thank you
I believe you want a join with an into
var bills = (from b in this._entities.Bills
join t in this._entities.Transactions on t.Bill.bID equals b.bID into tg
where b.bCustomerIDF == id
orderby b.bTimestamp descending
select new
{
b.bTotal,
b.bTimestamp,
b.Customer.cName,
Transactions = tg
}
).Take(10);
return bills;
I would have thought that you should just be able to add something like the following into your select:
transactions.Where(x=>x.Bill.bID == b.bID)`
That having been said I do also think it sounds like your object model is wrong. I'd have expected a Bill to have a collection of Transactions that are on that Bill.

restrict objects based on count of sub list in LINQ

I am well and truly stuck for some reason. I have a bunch of XML which via linq I have adjusted to fit into my DTO objects, this works fine, but I need an additional filter that only returns the Room Types that have rooms that have full availability for a period.
Now my original query to setup the DTO Works fine, but I would like to add something that only returns the rooms that have rates available for the entire periods, so say you want to book 10 days, you should only get the room types back that have the full 10 days available. My original query is the following:
var items = (
from rt in data.Descendants("RoomType")
select new RoomType
{
name = rt.Descendants("RoomDescription").Descendants("Text").SingleOrDefault().Value,
rooms = (
from r in rt.Descendants("Room")
select new Room
{
Name = r.Attribute("id").Value,
rates = (
from rr in r.Descendants("RoomRate")
where DateTime.Parse(rr.Attribute("EffectiveDate").Value) >= startDate
where DateTime.Parse(rr.Attribute("EffectiveDate").Value) <= endDate
select new RoomRate
{
EffectiveDate = DateTime.Parse(rr.Attribute("EffectiveDate").Value)
})
})
});
if it is at all possible to have the restriction in this query that would be amazing, but I couldn't see how to do it.
When I tried to create another query off the back of this one I didn't know how I could query the count of Rooms.RoomRates from the RoomType object to return. I tried
var result = items.Where(i => i.rooms.Where(r => r.rates.Count() == 10));
but that gives me an exception where it can't convert IEnumerable to bool, .Any() compiles but returns everything (as probably expected).
Does anyone know what I am doing wrong here?
EDIT: ** this is how it is pulling the data out at the moment
Room Type: Single
Room 1 (Days Available 10)
Room 2 (Days Available 10)
Room Type: Twin
Room 3 (Days Available 10)
Room 4 (Days Available 4)
what I am trying to do is exclude Room 4 from returning as it doesn't meet the days criteria
so what I should get back is:
Room Type: Single
Room 1 (Days Available 10)
Room 2 (Days Available 10)
Room Type: Twin
Room 3 (Days Available 10)
If you only want Rooms, you can just flatten the collection, then filter it:
IEnumerable<Room> rooms = items.SelectMany(i => i.Rooms)
.Where(r => r.rates.Count() == 10)
If you want RoomTypes, you'll need to create new RoomType objects with filtered Rooms:
var types = items.Select(i =>
new RoomType {
name = i.name,
rooms = i.rooms.Where(r => r.rates.Count() == 10)
}
);

Resources