Complex LINQ to SQL Query with dates - c#

I'm building a query with LINQ to SQL in my C# project, but I have some problems with it...
What I want to do, is select the 4 lasts days that are like today (For example, a Friday), so if we're on Friday 28, I want to query for: Friday 21, 14, 7... The last four Fridays but NOT today.
This is easy, I've done that but here's the complex part, I want to not query the exceptions I set, for example End of month, which are from 28th to 1st day of each month, so let's say i want to query this (october, fridays):
Today is Friday 26, I want to query:
19, 12, 5 and September 28th (the fourth friday from now), but as I said, 28th is end of month, so i need to return September 21th which is the last friday and it is not end of month... I have the same issues with holidays, but I think if I can handle end of months, I can do with them...
i hope I've explained good for you to understand what I want... Here's my query, which is working but can't handle exceptions. (the field b.day is the Id for each days, 8 means end of month, and 7 holiday)
var values =
from b in dc.MyTable
where // This means end of month
b.day != 8
// This triggers to query last 4 days
&& b.date == Convert.ToDateTime(last.ToString("dd/MM/yyy")).AddDays(-28)
|| b.date == Convert.ToDateTime(last.ToString("dd/MM/yyy")).AddDays(-21)
|| b.date == Convert.ToDateTime(last.ToString("dd/MM/yyy")).AddDays(-14)
|| b.date == Convert.ToDateTime(last.ToString("dd/MM/yyy")).AddDays(-7)
orderby b.id descending
group b.valor by b.hora_id into hg
orderby hg.Key descending
select new
{
Key = hg.Key,
Max avg = System.Convert.ToInt32(hg.Average() + ((hg.Average() * intOkMas) / 100)),
Min avg = System.Convert.ToInt32(hg.Average() - ((hg.Average() * intOkMenos) / 100))
};

You should prepare the list of days you'd like retrieve before trying to query:
// Get the last four days excluding today on the same weekday
var days = Enumerable.Range(1, 4).Select(i => DateTime.Today.AddDays(i * -7));
Then remove any days you don't want:
// Remove those pesky end-of-month days
days = days.Where(d => d.Day < 28 && d.Day > 1);
When you're done preparing the list of days you want to retrieve, only then should you perform your query:
from b in dc.MyTable
where days.Contains(b.date) // Translated to SQL: date IN (...)
...
EDIT: As you mentioned in your comment, you want a total of four days even after any filtering you perform. So simply generate more days and take the first four:
var days = Enumerable.Range(1, int.MaxValue - 1)
.Select(i => DateTime.Today.AddDays(i * -7))
.Where(d => d.Day < 28 && d.Day > 1)
.Take(4);
Due to the way LINQ (and in general, enumerators) work, only four days plus any skipped days will be calculated.

Building on Allon Guralnek's answer, I'd modify it slightly:
First, build an infinite date generator:
public IEnumerable<DateTime> GetDaysLikeMe(DateTime currentDate)
{
DateTime temp = currentDate;
while(true)
{
temp = temp.AddDays(-7);
yield return temp;
}
}
Then you can use deferred execution to your advantage by limiting to only dates that meet your additional criteria:
GetDaysLikeMe(DateTime.Now).Where(dt => /* dt meets my criteria */).Take(4)
Then you can use this list that was generated to query in your LINQ to SQL like Allon Guralnek suggested above:
from b in dc.MyTable
where days.Contains(b.date) // Translated to SQL: date IN (...)
...
This has the benefit of you being able to specify additional predicates for what are acceptable dates and still get at least 4 dates back. Just be sure to put some bounds checking on the infinite date generator in case one of your predicates always returns false for whatever reason (which means the generator will never exit).
IE: while(temp > currentDate.AddYears(-1))

I would highly suggest writing your exception code (last friday of the month) after retrieving your rows, as this logic seems to be too complicated for a LINQ statement. Instead of retrieving the last 4 days, retrieve the last 5. Remove any that are the last Friday of each respective months. If you still have 5 rows, remove the last one.
Update
var values1 =
from b in dc.MyTable
where // This means end of month
b.day != 8
// This triggers to query last 4 days
&& b.date == Convert.ToDateTime(last.ToString("dd/MM/yyy")).AddDays(-28)
|| b.date == Convert.ToDateTime(last.ToString("dd/MM/yyy")).AddDays(-21)
|| b.date == Convert.ToDateTime(last.ToString("dd/MM/yyy")).AddDays(-14)
|| b.date == Convert.ToDateTime(last.ToString("dd/MM/yyy")).AddDays(-7)
orderby b.id descending
select b;
//Do stuff with values
var values2 = from b in values2
group b.valor by b.hora_id into hg
orderby hg.Key descending
select new
{
Key = hg.Key,
Max avg = System.Convert.ToInt32(hg.Average() + ((hg.Average() * intOkMas) / 100)),
Min avg = System.Convert.ToInt32(hg.Average() - ((hg.Average() * intOkMenos) / 100))
};

Related

Counting active listings by month and year

I am currently working with real-estate data, and each Listing entity has a ListingDate and a CloseDate. What I am currently trying to do is to count how many Listings are active in a given month and year (group by year and month).
So for an example if Listing1 had an ListingDate of 05/01/2020 and a CloseDate of 08/01/2020, there would be 1 Active count for May, June, July, and August and a year total of 4.
I am using EF and LINQ, and was wondering if I could solve it somehow.
Any help or advice is appreciated.
Sure you can; if you map listings to each month in which it's active, you can then simply group the results by month and get the counts trivially. Thus, the trickiest part is to just come up with the month DateTime values, which isn't that tricky.
Extension method to get month DateTimes from a start and end date:
public static IEnumerable<DateTime> GetMonths(this DateTime startDate, DateTime endDate)
{
var monthDiff = (endDate.Month - startDate.Month) + (12 * (endDate.Year - startDate.Year));
var startMonth = new DateTime(startDate.Year, startDate.Month, 1);
return Enumerable.Range(0, monthDiff + 1)
.Select(i => startMonth.AddMonths(i));
}
Create lookup:
var listingsByMonth = listings
.SelectMany(l =>
{
return l.ListingDate.GetMonths(l.ClosingDate.AddDays(-1)) // assuming closing date is exclusive
.Select(dt => new KeyValuePair<DateTime, Listing>(dt, l));
})
.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
Demonstration of results:
foreach(var g in listingsByMonth)
{
Console.WriteLine($"{g.Key:yyyy-MM}: {g.Count()}");
}
Fiddle
Let's assume that date is given in DateTime structs. (You can parse text input to DateTime, check this) We can iterate over a List containing Listing entities, and perform a check to see if given date is in the range of ListingDate and ClosingDate. If the check succeeds, copy the entity to another list.
DateTime query = ...;
List<Listing> list = ...;
List<Listing> pass = new();
foreach (Listing entity in list)
{
if (entity.ListingTime < query && query < entity.ClosingTime)
pass.Add(entity)
}
While checking whether the query is in range, we could've used DateTime.Compare() but less than/greater than operators make the statement easier to read.

Getting records upon Financial year 6th April to 5th April using C# and Linq

I have a table in which i have Transaction date and i want to get records from last financial year.
for ex. If today is 17-03-2017 i want to get records from 06-04-2017 to till today. then if my today's date is more than 6th April then get records from this year 06-04-2017 to till today.
How can i get records using c# and linq with this condition.?
I have used this linq query
List<TransactionMaster> donations =
db.TransactionMasters.Where(s => s.DonorId == DonorId &&
s.TransactionDate.Year >= DateTime.Now.Year - 1 &&
s.TransactionDate.Month >= 4 && s.TransactionDate.Day >= 6)
.ToList();
but i get records from only year 2016 and month > April.
So how can i get records for financial year 6th April to 5th April?
Try to compare the dates directly instead of parts, such that:
var date = new DateTime(DateTime.Now.Year, 4,6);
List<TransactionMaster> donations =
db.TransactionMasters.Where(s => s.DonorId == DonorId &&
s.TransactionDate >= date).ToList();

Linq Group by Week Number Including 0 Record Weeks

I'm trying to get a dataset for an SSRS Line Chart. I am trying to show the number of records per week of the year for the last six months. In order for the line chart to be accurate, I have to show weeks that have zero records.
I want to provide the line chart with data that looks like this:
Year Week Count
2014 52 13
2015 1 0
2015 2 16
The following linq query gets everything I need minus the weeks that have zero records:
list = (from t in context.Cats.Where(
t => t.Name == "Fluffy" &&
(t.Recorded >= start && t.Recorded <= end)
).AsEnumerable()
group t by new { Year = t.Recorded.Year, WeekNumber = (t.Recorded - new DateTime(t.Recorded.Year, 1, 1)).Days / 7 } into ut
select new TrendRecord
{
Year = ut.Key.Year,
Week = ut.Key.WeekNumber,
Count = ut.Count()
}).OrderBy(t => t.Year).ThenBy(t => t.Week).ToList<TrendRecord>();
I've looked at this and this SO questions but neither seems to quite fit my predicament.
I'm leaning towards creating a list with all possible week numbers between the start and end dates and then left joining my data to it somehow so that weeks with 0 records show up.
Is this an acceptable approach to this problem(please show me how if so)? Is there a way to do this in one query without the need for a separate list to join to?
You will need a list of weeks and do some kind of LEFT OUTER JOIN using Linq.
Something like this should help you get started
var weeks = new[]
{
new {Year = 2014, Week = 52},
new {Year = 2015, Week = 1},
new {Year = 2015, Week = 2},
};
var listWithEmptyWeeksIncluded =
from w in weeks
from l in list.Where(x => x.Year == w.Year && x.Week == w.Week).DefaultIfEmpty()
select new TrendRecord
{
Year = w.Year,
Week = w.Week,
Count = l == null ? 0 : l.Count
};
Of course, the weeks should be generated on the basis of your variables start and end
Also, you should be using a standard week calculation, for instance the ISO week standard, so that weeks that span two years are handled correctly.

How to calculate sum of amount for each 5 weeks before in linq?

I am having the amount field in income table in database, as well as the created date in same table. I need data like,
Week 1 => Sum(amount for week 1)
Week 2 => Sum(amount for week 2)
Week 3 => Sum(amount for week 3)
Week 4 => Sum(amount for week 4)
Week 5 => Sum(amount for week 5)
What should be my linq query. I am using entity framework.
Edited:
Say previous 4 week of current week + current week =5 weeks. here current week is the week of today's date. eg. today is 26'th Aug 2014 so current week is from 24'th Aug 2014 (Sunday) to 30'th Aug 2014 (Saturday).
You can use the methods in EntityFunctions to perform date and time arithmetic. So you should start by working out the start and end dates, then use TruncateTime if necessary to truncate your created date to a date (instead of date and time), and use DiffDays to work out "number of days since the start of the period". Then just divide by 7 to group...
DateTime today = DateTime.Today;
DateTime start = today.AddDays(-(int) today.DayOfWeek) // Sunday...
.AddDays(-28); // 4 weeks ago
DateTime end = start.AddDays(7 * 5);
var result = from entry in db.Entries
where entry.Created >= start && entry.Created < end
group entry.Amount by EntityFunctions.DiffDays(start,
EntityFunctions.TruncateTime(entry.Created)) / 7 into g
select new { Week = g.Key + 1, Sum = g.Sum() };
While I'd expect that to work, I haven't personally done any date/time work in EF myself. The general approach should be fine, it's just that you may need to tweak it. Also note that this won't give you any results for weeks that don't have any entries - it's probably easiest to do that outside EF.
EDIT: If the summing part isn't working, it's easy to do the summing locally instead:
var query = from entry in db.Entries
where entry.Created >= start && entry.Created < end
group entry.Amount by EntityFunctions.DiffDays(start,
EntityFunctions.TruncateTime(entry.Created)) / 7;
var result = query.AsEnumerable() // Execute the rest locally
.Select(g => new { Week = g.Key + 1, Sum = g.Sum() });

Group by Week of year (Week number) in Linq to SQL

In regular SQL i could do something like
SELECT * From T GROUP BY DATEPART(wk, T.Date)
How can i do that in Linq to SQL ?
The following don't work
From F In DB.T Group R By DatePart(DateInterval.WeekOfYear, F.Date)
Also don't work:
From F In DB.T Group R By (F.Date.DayOfYear / 7)
LINQ to SQL does not support the Calendar.WeekOfYear method, but you could potentially create a TSQL function that wraps the call to DatePart. The DayOfYear / 7 trick should work for most cases and is much easier to use. Here's the code I ended up with:
var x = from F in DB.T
group F by new {Year = F.Date.Year, Week = Math.Floor((decimal)F.Date.DayOfYear / 7)} into FGroup
orderby FGroup.Key.Year, FGroup.Key.Week
select new {
Year = FGroup.Key.Year,
Week = FGroup.Key.Week,
Count = FGroup.Count()
};
Results in something like this:
Year Week Count
2004 46 3
2004 47 3
2004 48 3
2004 49 3
2004 50 2
2005 0 1
2005 1 8
2005 2 3
2005 3 1
2005 12 2
2005 13 2
You can use the SqlFunctions.DatePart method from the System.Data.Entity.SqlServer namespace.
// Return the week number
From F In DB.T Group R By SqlFunctions.DatePart("week", F.Date)
Range variable name can be inferred only from a simple or qualified name with no arguments
This works correctly.
from F in DB.T group F by F.Date.DayOfYear / 7;
You were specifying the group by incorrectly. The result of this code be a collection of objects. Each object will have a Key property which will be what you grouped by (in this case the result of F.Date.DayOfYear / 7. Each object will be a collection of objects from T that met the group condition.
If you are concerned about the culture you are in the following code will take that into account:
var ci = CultureInfo.CurrentCulture;
var cal = ci.Calendar;
var rule = ci.DateTimeFormat.CalendarWeekRule;
var firstDayOfWeek = ci.DateTimeFormat.FirstDayOfWeek;
var groups = from F in DB.T
group F by cal.GetWeekOfYear(F, rule, firstDayOfWeek) into R
select R;
First you should get the date of the first day in the week.
To get the date of the first day in the week.
you can use this code:
public static class DateTimeExtensions
{
public static DateTime StartOfWeek(this DateTime dt, DayOfWeek startOfWeek)
{
int diff = dt.DayOfWeek - startOfWeek;
if (diff < 0)
{
diff += 7;
}
return dt.AddDays(-1 * diff).Date;
}
}
Then you can group by the first date of the week.
So this code in regular SQL :
SELECT * From T GROUP BY DATEPART(wk, T.Date)
can be done in Linq to SQL like this
T.GroupBy(i => i.Date.StartOfWeek(DayOfWeek.Monday));

Categories

Resources