This is a follow up question to the one answered here: Excluding dates from Linq Query in MVC .net application - which I'm very grateful for.
I'm hoping that someone can check my syntax in my Linq query below - to confirm if it's the best way to build the query up, or if my use of the syntax is inefficient.
public class Room
{
public int RoomId { get; set; }
[Display(Name = "Room Name")]
public string Name { get; set; }
public bool Disabled { get; set; }
public virtual ICollection<Client> Clients { get; set; }
}
public class Client
{
public int ClientId { get; set; }
public int RoomId { get; set; }
public string ClientName { get; set; }
public DateTime Arrival { get; set; }
public DateTime Departure { get; set; }
public virtual Room Room { get; set; }
}
Clients lists a row for each client who has a particuar room booked. I have 3 rooms, Room 1, Room 2, and Room 3. So entries in the client table could be:
Client 1, Room 1, Mr Smith, Arr: 2012-07-08, Dep: 2012-07-10
Client 2, Room 1, Mr Jones, Arr: 2012-07-14, Dep: 2012-07-20
Client 3, Room 2, Mr Alas, Arr: 2012-07-12, Dep: 2012-07-15
Given an arrival and departure date, I'm trying to take my whole list of rooms, and take away any that have a client staying where the arrival or departure dates overlap. So using the data above, if I had an arrival date of 2012-07-12 and a departure date of 2012-07-13, then Room 2 would not be available, however, Room 1, does not have any bookings spanning that date - so Room 1 I want to leave in my result set.
So my Linq query (I'm new to Linq, so please point out where I may be going wrong) is:
var dteFrom = DateTime.Parse("2012-07-12");
var dteTo = DateTime.Parse("2012-07-13");
var rooms = (from r in Rooms
where !r.Clients.Any(
client =>
( dteFrom >= client.Arrival && dteFrom <= client.Departure )
||
( dteTo >= client.Arrival && dteFrom <= client.Departure )
||
( dteFrom <= client.Arrival && dteTo >= client.Departure )
)
select r);
Given that I'm looking to include ALL rooms, EXCEPT any that meet the criteria, can anyone confirm that my use of .Any and ! and || are correct, as far as LINQ goes?
Is there any better way within the syntax, of excluding records from the Rooms list?
Thank you again for any help,
Mark
Looks fine to me - one thing that may help readability would be to compose your query in two steps:
var prebookedRooms = rooms
.Where(room => room.Clients.Any(client =>
(dteFrom >= client.Arrival && dteFrom <= client.Departure) ||
(dteTo >= client.Arrival && dteFrom <= client.Departure) ||
(dteFrom <= client.Arrival && dteTo >= client.Departure)));
var freeRooms = rooms.Except(prebookedRooms);
Remembering that the query is only executed, when the results are enumerated - so there's no performance cost to doing this in two steps.
Related
I am trying to count no of leaves condition year and month here is my LINQ query-
leaves = ( from x in obj.Result
where (Int64.Parse(x.status) == 1 &&
((x.start_date.Month == month &&
x.start_date.Year == selectedyear) &&
(x.end_date.Year == selectedyear || x.end_date.Month == month)))
orderby x.user_id
select x).ToList();
and I am getting a perfect result but now my start_date and end_date becomes
the array of dates, in that array multiple leaves dates are stored
now I want to check my all record within that array condition year and month
here is my model example
public DateTime start_date { get; set; }
public DateTime end_date { get; set; }
public List<string> Leaves_Date { get; set; }
and here also leaves_Date would be a string so someone has an idea that how can I set array in my LINQ query
In my Entity Framework application, I have an Entity called Invoice.cs, it has various properties, but here are the ones we're concerned about for the question:
public class Invoice : IEntity
{
public int Id { get; set; }
public decimal Amount { get; set; }
public DateTime Date { get; set; }
public int OrderId { get; set; }
public virtual ICollection<Payment> Payments { get; set; }
}
I am attempting to query the database to get a list of all outstanding invoices. An outstanding invoice is the following:
If the total of the payments made against the Invoice are less than the Invoice Amount, then the invoice is Outstanding.
I'm stuck on working out if an invoice is outstanding or not in my LINQ query, which currently looks like this:
var outstandingInvoices = from inv in _context.Invoices
where !inv.IsDeleted && inv.Date >= startDate && inv.Date <= endDate
select inv;
startDate and endDate are parameters passed in to filter the result.
Another complexity with this is to do with how payments are made. The payments can be made in RMB (Chinese currency) or GBP (UK currency), and in the report I'm generating, I want all to be displayed as GBP.
So I must somehow add this logic too:
// loop through payments for each invoice, and add payment
// amount to a local variable called total
if (payment.Currency == Currency.Gbp)
{
total += payment.Amount;
} else
{
total += payment.Amount / (decimal)payment.ConversionRate;
}
The Payment.cs entity has these two properties that are of concern:
public class PaymentViewModel
{
public int Id { get; set; }
[Required]
public decimal Amount { get; set; }
[Required]
[Display(Name = "Payment Currency")]
public Currency Currency { get; set; }
[Display(Name = "Conversion Rate")]
public float ConversionRate { get; set; }
}
You are going to have an issue because of the float/decimal differences. Depending on your database provider, it might allow you to force the cast from decimal to float (or it might not). Of course, then you'll have issues of payments that are really close, but not quite the same. What if the amount / CoversionRate is 0.999999999999999999999999999 GBP when the amount was for 1 GBP? Technically it's not fully paid.
Ultimately, the conversion rate should also be a decimal, not float, but determining the precision depends on your source. Is it accurate to 5 decimal places or 7?
var outstanding = _context.Invoices
.Where(x=>x.Date >= startDate)
.Where(x=>x.Date <= endDate)
.Where(x=>!x.IsDeleted)
.Where(x=>x.Payments.Sum(p=>(float)p.Amount / p.ConversionRate) < x.Amount);
Alternatively if the total paid is within 1 GBP:
var outstanding = _context.Invoices
.Where(x=>x.Date >= startDate)
.Where(x=>x.Date <= endDate)
.Where(x=>!x.IsDeleted)
.Where(x=>x.Payments.Sum(p=>(float)p.Amount / p.ConversionRate) - x.Amount < 1);
You can use ternary operator to adjust payment amount in case it is not in GBP:
var outstandingInvoices =
from inv in _context.Invoices
let totalPayment = inv.Payments.Sum(p =>
p.Currency == Currency.Gbp ? p.Amount : p.Amount / p.ConversionRate)
where !inv.IsDeleted
&& inv.Date >= startDate && inv.Date <= endDate
&& totalPayment < inv.Amount
select inv;
I am trying to get single record from statistics table with following logic:
1st preference : Flag = 0
2nd preference : Flag = 1
3rd preference : Flag = 2 (only if we dont have records with flag=0 and 1)
Table: Statistics
Id Zoneid Emergency Flag Date
1 100 0 1 2016-6-01 13:10:05.360
2 100 2 2 2016-6-01 14:10:05.360
3 100 0 2 2016-6-01 15:10:05.360
4 100 2 2 2016-6-01 16:10:05.360
5 100 2 0 2016-6-01 14:10:05.360
6 100 1 2 2016-6-01 13:10:05.360
The logic I am trying to implement is like below:
If(Flag == 0) then
take records with highest emergency(order by emergency desc) but if multiple records found then take latest record order by date desc(only 1 ).
else if(flag==1)
Take records with highest emergency(order by emergency desc) but if multiple records found then take latest record order by date desc(only 1).
else if (no records with flag==0 and flag==1 found)
Take records with highest emergency(order by emergency desc) but if multiple records found then take latest record order by date desc(only 1).
Data model:
public partial class Zone
{
public int Id { get; set; }
public string Area { get; set; }
public virtual ICollection<Statistics> Statistics { get; set; }
}
public partial class Statistics
{
public int Id { get; set; }
public int ZoneId { get; set; }
public int Emergency { get; set; }
public int Flag { get; set; }
public System.DateTime Date { get; set; }
public virtual Zone Zone { get; set; }
}
My query:
var statistics= (from z in db.Zone
select new
{
ZoneName = z.Area,
//Not getting how t implement multiple conditions in EF
StatisticsId = z.Statistics.Where(t => t.ZoneId == 100 &&)
.Select(t => t.Id).FirstOrDefault()
}
So here I am not getting how to implement all those conditions and get desired statistic record.
The logic you are describing sounds like simple priority order - first by Flag ascending, then (for equal Flag) by Emergency descending, then (for equal Flag and Emergency) by Date descending, and taking the first record in that order:
StatisticsId = (from s in z.Statistics
where s.ZoneId == 100
orderby s.Flag, s.Emergency descending, s.Date descending
select (int?)s.Id).FirstOrDefault() ?? 0
Have a shot with this (warning: untested, don't have the time to re-create the class and all):
var statistics= (from z in db.Zone
select new
{
ZoneName = z.Area,
StatisticsId = z.Statistics.OrderBy(t=>t.Flag)
.Where(t => t.ZoneId == 100).Select(t => t.Id).FirstOrDefault()
});
Let's say I have a model football Team. It plays Matches in a group with other teams. Now I want to select top 2 teams from the list. The score is counted as usual: 3 for the win, 1 for the draw, 0 for the loss.
The model for Match looks like this:
[Key]
public int MatchId{get;set;}
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
[ForeignKey("HomeTeamId")]
public virtual Team HomeTeam { get; set; }
[ForeignKey("AwayTeamId")]
public virtual Team AwayTeam { get; set; }
public int? HomeTeamScored { get; set; }
public int? AwayTeamScored { get; set; }
I've tested this 5 solutions:
1) have a view instead of the table to take the score column from, but it complicates the programming part since I will have to somehow tell EF to use the table for insertion but the view for showing the data
2) Have the column Score as not mapped, then take all teams, count the score like this:
var list = db.Teams.ToList();
foreach(var team in list)
{
team.Score = db.Matches.Where(...).Sum();
}
then just order the list by Score and take first 2.
3) Another way is to have
var list = db.Teams.OrderByDesc(t => db.Matches.Where(...).Sum()).Take(2).ToList();
I will have to do a lot of checking for null, also checking which team won or drawn, whether the team I was looking for played as home or away, etc.
4)
Yet another option would be to recount the Score for the team every time I add/edit a match, however I feel like it is a very unprofessional approach.
As I said, each of these methods are solutions that would lead me to solving the task, but... I am having a sixth sense that I am missing something completely obvious as of how I could make this with the least effort. Can anyone suggest what I am missing?
P.S. If it affects the answer, let's assume I am using the latest version of everything.
When data redundancy looms, often normalization is the solution. I think in your case you need a bit of both, normalization and a bit of redundancy.
The repeated properties in Match are "smelly". They seem to call for normalization. A closer look reveals that this is not true for all of them. A match consists of two teams, always. So the two TeamIds are OK (and the accompanying references). But you could store the scores differently.
Take a look at this possible model:
class Team
{
public int TeamId { get; set; }
// ...
public ICollection<MatchTeam> MatchTeams { get; set; }
}
class Match
{
public int MatchId { get; set; }
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
public virtual Team HomeTeam { get; set; }
public virtual Team AwayTeam { get; set; }
}
class MatchTeam
{
public int MatchId { get; set; }
public int TeamId { get; set; }
public int Scored { get; set; } // Number of goals/points whatever
public int RankingScore { get; set; } // 0, 1, or 3
}
MatchTeam is an entity that stores the achievements of 1 team in 1 match. The Scored property is normalized result of HomeTeamScored and AwayTeamScored. The advantage is: the property isn't nullable: a MatchTeam entry is created when the results are a fact.
The redundancy is in the RankingScore property. This has to be determined when a match is entered or modified and it depends on (and should be consistent with) the scores. As always with redundancy, there's the danger of data inconsistency. But is it a big danger? If there is only one service method by which MatchTeam data are entered or modified the danger is confined sufficiently.
The advantage is that now it's doable to collect the total scores for each team at runtime:
var topTeams = context.Teams
.OrderByDescending(t => t.MatchTeams.Sum(mt => mt.RankingScore))
.Take(2);
1) I don't understand why implementing a view complicates your programming at at all. That's a good solution. Inserting a match result and getting the top teams are two completely independent operations. Please, let me see some code to understand why you have that strong coupling between them suchs independent things as a match score and a total score of a team.
2) This is a bad option: you need to make a query for all teams, and an additional query for each team. Bad performance!
3) It would be good to see your code to show how you can improve your query. For example making null checking is not that bad. It's as simple as using the ?? operator, i.e. mt => mt.HomeTeamSocred ?? 0 will convert null to 0 quite easily. If you showed the used expression it would be possible to see if it can be improved and simplified. However,I can propose this one, which is not that complicated:
ctx.Match.Select(m => new
{ // Score for HomeTeam
TeamScore = (m.HomeTeamScored ?? 0) > (m.AwayTeamScored ?? 0)
? 3 : (m.HomeTeamScored ?? 0) < (m.AwayTeamScored ?? 0)
? 0 : 1,
TeamId = m.HomeTeamId,
})
.Concat(
ctx.Match.Select(m => new
{ // Score for away Team
TeamScore = (m.HomeTeamScored ?? 0) > (m.AwayTeamScored ?? 0)
? 0 : (m.HomeTeamScored ?? 0) < (m.AwayTeamScored ?? 0)
? 3 : 1,
TeamId = m.AwayTeamId,
})
).GroupBy(mr => mr.TeamId) // Group match scores by TeamId's
.Select(mrs=> new
{
TeamId = mrs.Key,
TotalScore = mrs.Sum(m => m.TeamScore)
})
.OrderByDescending(ts => ts.TotalScore)
.Take(2);
However there is something I don't understand. Why a Home/AwayTeamScored can be null? Since before the match starts, the Score must be zero for both teams. That null makes no senses. This would avoid the trobule with checking nulls.
4) What does this option mean?
I have a picturelikes table in my database. For every like a new picture like table row will be created. I want to group those likes by lastweek and pictureid, meaning all the likes for one single pic in the last week should be accumulated and become one like.Then i will be able to sort them by thier count.
This is my picturelike table :
public partial class picturelike
{
public int idpicturelike { get; set; }
public int idpictures { get; set; }
public int iduser { get; set; }
public System.DateTime iddatetime { get; set; }
public int iduserlikedby { get; set; }
public int likenumber { get; set; }
}
Till now i am able to write this code, this is my web api controller code using linq.
var combo = from p in db.picturelikes
group p by new {p.iddatetime, p.idpictures } into pgroup
let count = pgroup.Count()
orderby count descending
select new SortedDto
{
IdPictures=pgroup.FirstOrDefault().idpictures,
IdPictureLike = pgroup.FirstOrDefault().idpicturelike,
IdUser = pgroup.FirstOrDefault().iduser,
IdDatetime = pgroup.FirstOrDefault().iddatetime,
IdUserLikedBy = pgroup.FirstOrDefault().iduserlikedby,
LikeNumber = pgroup.FirstOrDefault().likenumber
};
return combo;
So what else i need to do, how to make this code working, i am stuck here, how should i do it?
You cannot group by the last seven days. You have to filter first (where), then group:
var pastDate= DateTime.Now.Date.AddDays(-7);
var combo = from p in db.picturelikes
where p.iddatetime.Date > pastDate
group p by p.iddatetime.Date into pgroup
let count = pgroup.Count()
orderby count descending
select new SortedDto
{
IdPictures=pgroup.FirstOrDefault().idpictures,
IdPictureLike = pgroup.FirstOrDefault().idpicturelike,
IdUser = pgroup.FirstOrDefault().iduser,
IdDatetime = pgroup.FirstOrDefault().iddatetime,
IdUserLikedBy = pgroup.FirstOrDefault().iduserlikedby,
LikeNumber = pgroup.FirstOrDefault().likenumber
};
return combo;
An explaination of this line:
where p.iddatetime.Date > DateTime.Now.Date.AddDays(-7)
The Date property is just the date component of the time. We snap to midnight and filter by the logical days of the week, not by hours. This will give you more palatable results but feel free to change it.
To select only the rows from the last week, you can add a where clause to your linq statement, e.g.
where p.iddatetime > (DateTime.Today - TimeSpan.FromDays(7))