Fetch data by upcoming birthdays in linq to sql - c#

I want to show list of people whose birthdays are in coming 15 days. I have below dates in my table column:
LET ME MORE CLEAR MY QUESTION
BELOW IS MY EMPLOYEE TABLE COLUMNS
EMP_ID |EMP_TYPE |EMP_USERNAME |EMP_DOB
======= |========== |=============== |==================
1 |ADMIN |ELENA GILBERT |1993-02-19
2 |EMPLOYEE |KATHERINE PIERCE |1993-03-19
3 |EMPLOYEE |STEFAN SALVATORE |1993-04-19
4 |EMPLOYEE |DAMON SALVATORE |1993-05-19
5 |EMPLOYEE |JEREMY GILBERT |1993-05-20
Now I just want to show upcoming birthdays in 15 days.
Below I created a custom class in which I set two properties:
public class Birthday
{
public string Name { get; set; }
public DateTime date { get; set; }
}
Below is my web method which return me a list from which I want just Emp_Username and Emp_DOB which upcoming within 15 days.
[WebMethod]
public static List<Birthday> getBirthday()
{
var slist = new List<Birthday>();
var db = new BLUEPUMPKINEntities();
var query = (from emp in db.Employees
let BirthdayDiff = (new DateTime(DateTime.Now.Year,
emp.EMP_DOB.Value.Month, emp.EMP_DOB.Value.Day) - DateTime.Now).TotalDays where BirthdayDiff >= 0 && BirthdayDiff <= 15
select new Birthday { Name = emp.EMP_USERNAME, date = Convert.ToDateTime(emp.EMP_DOB) });
return slist.ToList();
}
Problem is my above code is not working and not showing any errors in de-bugging.

You should change your query to something like this and then return it:
[WebMethod]
public static List<Employee> getBirthday()
{
var db = new BLUEPUMPKINEntities();
const int dateOffset = 15;
var today = DateTime.Today;
var maxDate = DateTime.Today.AddDays(dateOffset);
return (from emp in db.Employees
where emp.EMP_DOB.Value >= today
where emp.EMP_DOB.Value <= maxDate
select emp).ToList();
}

There are at least three issues in your code.
Firstly, this line can potentially produce incorrect results:
let BirthdayDiff = (new DateTime(DateTime.Now.Year, emp.EMP_DOB.Value.Month, emp.EMP_DOB.Value.Day) - DateTime.Now).TotalDays
Note that you use the current time to produce year
DateTime.Now.Year
And consider the following case:
Now is 25-Dec-15 and One of your Employee is having birthday in
3-Jan-16. According to the calculation, you would produce DateTime
with value of 3-Jan-15 for your Employee and you minus it with
DateTime.Now and thus you will get value < -300 in total days.
Secondly, don't use DateTime.Now more than once in a single query, because the result of the subsequent DateTime.Now may be different from the first one. Use only once:
DateTime now = DateTime.Now; //and then just use now
Or even better, to remove all hours and minutes discrepancy:
DateTime today = DateTime.Today;
And lastly, you never return the result of the query, but only an empty List.
Note that you define:
var slist = new List<Employee>();
And the query:
var db = new BLUEPUMPKINEntities();
var query = from emp in db.Employees
let BirthdayDiff = (new DateTime(DateTime.Now.Year, emp.EMP_DOB.Value.Month, emp.EMP_DOB.Value.Day) - DateTime.Now).TotalDays
where BirthdayDiff >= 0 && BirthdayDiff <= 15
select emp;
But you neither relate your slist with query nor return the query itself. Thus, you always get nothing, because slist is always an empty, new List.
Minor Edit: change from db to db.Employees and adding ToList()
Correct three of them and you have a safe way to do get what you want (Note: beware of leap year):
[WebMethod]
public static List<Employee> getBirthday()
{
var slist = new List<Employee>();
var db = new BLUEPUMPKINEntities();
var today = DateTime.Today; //2.
return (from emp in db.Employees
let BirthdayDiff = (new DateTime(today.Year, emp.EMP_DOB.Value.Month, emp.EMP_DOB.Value.Day) - today).TotalDays
let TrueBirthdayDiff = BirthdayDiff >= 0 ? BirthdayDiff : BirthdayDiff + 365 + Convert.ToInt32(DateTime.IsLeapYear(now.Year)) //1, 3 and leap year
where TrueBirthdayDiff >= 0 && TrueBirthdayDiff <= 15
select emp).ToList();
}

By using below query I found a way to know upcoming birthdays. I don't know why folks down voted my this post.
var day = DateTime.Now.Day;
var month = DateTime.Now.Month;
var query = (from emp in db.Employees
where emp.EMP_DOB.HasValue == true
&& emp.EMP_DOB.Value.Day >= day && emp.EMP_DOB.Value.Month == month
select new Birthday
{
Name = emp.EMP_USERNAME,
date = emp.EMP_DOB.Value
}).ToList();
return query;

You're returning an empty list...
[WebMethod]
public static IList<Employee> getBirthday() //<-- changed signature to IList
{
var slist = new List<Employee>();
var db = new BLUEPUMPKINEntities();
var query = from emp in db.Employees
let BirthdayDiff = (new DateTime(DateTime.Now.Year,
emp.EMP_DOB.Value.Month, emp.EMP_DOB.Value.Day) - DateTime.Now).TotalDays
where BirthdayDiff >= 0 && BirthdayDiff <= 15
select emp;
//return slist; //<-- problem right here!
return query.ToList(); //<-- this should fix it...!
}
If your heart and soul is really tied to that List<T>, then do this right before the return slist:
slist.AddRange(query);
HTH!

Related

Linq Entity Framework grouping and joins on names, year and month

I am developing an ASP.NET MVC Appliction with MYSQL and Entity Framework. I have customers making payments made to some payment categories. I want to develop a ledger where I have customer names and all the classes of payments with total payments made for any particular pay category by each customer. And I will have to group by name, year and month. And be able to filter by year, month or customerid. I have 25 payment categories.
The solution I have made is ancient- it makes use of 25 joins to the get the categories, sum the payments and assign these to a viewmodel. This runs on my developing machine alright but it fails on my server with the error
MySql.Data.MySqlClient.MySqlException: Too high level of nesting for select
Can anyone give me a better approach to achieving this goal
My Code is like below
public ActionResult LedgerReport(int? year, int? month, int? CuId)
{
var query = LedgerYrMn(year, month, CuId);
return View(query);
}
public IEnumerable LedgerYrMn(int? year, int? month, int? CuId)
{
DateTime lastmonth = DateTime.Now;
DateTime thismonth = DateTime.Now;
DateTime thismonthstart = DateTime.Now;
if (year.HasValue && month.HasValue)
{
lastmonth = (new DateTime(year.Value, month.Value, DateTime.DaysInMonth(year.Value, month.Value)).AddMonths(-1));
thismonth = (new DateTime(year.Value, month.Value, DateTime.DaysInMonth(year.Value, month.Value)));
thismonthstart = (new DateTime(year.Value, month.Value, 1));
}
var query =
(
from bk in db.Customers
// pay category 1
join tr in db.Payments
.Where(a => a.PDate <= thismonth && a.PDate >= thismonthstart && a.paycategory.Id == 1 && a.Paid=true)
on bk.CustomerID equals tr.CustomerID into trs
// pay category 2
join tr2 in db.Payments
.Where(a => a.PDate <= thismonth && a.PDate >= thismonthstart && a.paycategory.Id == 2 && a.Paid=true)
on bk.CustomerID equals tr2.CustomerID into trs2
......
etc to the 25 payment categories.
}
select new LedgerViewModel
{
food=(int?)trs.Sum(c => c.Pay) ?? 0,
fuel=(int?)trs.Sum(c => c.Pay) ?? 0
.. etc
});
return query.AsEnumerable();
}

linq to retrieve name instead of id and list in descending order

can u help me to solve this.
i'm retrieving the balance of each heads, and i retrieved the balance of each heads. Now i want to list the balance in the descending order and list the name instead of h_id. i used the code
protected void account_watchlist() {
using(var context = new sem_dbEntities()) {
//ledger && head
var year = DateTime.Now.AddMinutes(318).Year;
var month = DateTime.Now.AddMinutes(318).Month;
var start = new DateTime();
if (month >= 4) {
start = new DateTime(year, 04, 01);
} else if (month < 4) {
start = new DateTime(year - 1, 04, 01);
}
var qr = (from a in context.ledgers
where a.entry_date >= start && a.entry_date < new DateTime(year, month, 1)
join b in context.heads on a.h_id equals b.h_id
group a by a.h_id into sb select new {
sb.FirstOrDefault().h_id,
totalc = sb.Sum(c => c.credit),
totald = sb.Sum(d => d.debit),
balance = sb.Sum(d => d.debit) - sb.Sum(c => c.credit)
}).ToList();
Repeater2.DataSource = qr.ToList();
Repeater2.DataBind();
}
}
You need to use group join of heads with ledgers. It will give you access both to head entity and all related ledgers (in headLedgers collection):
from h in context.heads
join l in context.ledgers.Where(x => x.entry_date >= startDate && x.entry_date < endDate)
on h.h_id equals l.h_id into headLedgers
where headLedgers.Any()
let totalc = headLedgers.Sum(l => l.credit),
let totald = headLedgers.Sum(l => l.debit),
select new {
h.h_id,
h.name,
totalc,
totald,
balance = totald - totalc,
}
I also introduced two range variables for total credit and total debit (consider better names here) to avoid calculating them second time for balance.

How to use SkipWhile with multiple conditions

I'm trying to filter objects with SkipWhile but it does not evaluate multiple conditions.
Below is a sample code demonstrating the issue,
int[] numbers = { 1, 2, 3, 4, 5 };
var result = numbers.SkipWhile(n => n < 2 && n != 2).ToList();
This query selects 2,3,4,5, which omits the second condition (n != 2), when the first condition is true.
Is it possible to make the query evaluate both conditions?
Edit:
My actual condition is something like
... dateRanges
.OrderBy(d=>d.Sequence)
.SkipWhile(d => d.FromDate <= currentDate && d.ToDate >= currentDate)
.Skip(1).First();
which is operating on DateTime filed, to select next object in the list
Edit 2:
I have created a sample program, which is something similar to my actual code
Class to hold data,
public class DateRange
{
public int Sequence { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
}
Program
static void Main(string[] args)
{
var dateRanges = new List<DateRange>(){
new DateRange{Sequence = 1 , FromDate = new DateTime(2014,1,1), ToDate = new DateTime(2014,1,31)},
new DateRange{Sequence = 2 , FromDate = new DateTime(2014,2,1), ToDate = new DateTime(2014,2,28)},
new DateRange{Sequence = 3 , FromDate = new DateTime(2014,3,1), ToDate = new DateTime(2014,3,31)},
new DateRange{Sequence = 4 , FromDate = new DateTime(2014,4,1), ToDate = new DateTime(2014,4,30)},
new DateRange{Sequence = 5 , FromDate = new DateTime(2014,5,1), ToDate = new DateTime(2014,5,31)},
};
var myDate = new DateTime(2014, 2, 10); // A Date in Fabruary
//This query selects {2, 2014/2/1, 2014/2/28}
var selectedItem = dateRanges.OrderBy(d => d.Sequence)
.Where(d => d.FromDate <= myDate && d.ToDate >= myDate)
.First();
//What I actually need to select is {3, 2014/3/1, 2014/3/31}
//Which is next item of the list
//This is what i have tried
//But this query also selects {2, 2014/2/1, 2014/2/28}
var nextItem = dateRanges.OrderBy(d => d.Sequence)
.SkipWhile(d => d.FromDate <= myDate && d.ToDate >= myDate)
.Skip(1).First();
//Because, results of this part of query returns objects from {1, 2014/1/1, 2014/1/31} ...
var unexpectdItems = dateRanges.OrderBy(d => d.Sequence)
.SkipWhile(d => d.FromDate <= myDate && d.ToDate >= myDate);
}
It is evaluating both conditions - but as soon as the condition is false, the rest of the sequence is returned. As soon as n==2, n < 2 && n != 2 is false. In fact, your condition makes no sense anyway - if n is less than 2 it can't be equal to 2.
Basically it's not clear what you're trying to achieve, but the condition you're using isn't appropriate - and if you want to check your condition on every value rather than just "values until the condition isn't met" then you should use Where instead of SkipWhile.
EDIT: Now that you've posted a complete example, we can see what's wrong. Look at your condition:
SkipWhile(d => d.FromDate <= myDate && d.ToDate >= myDate)
Now look at the first item of your data:
new DateRange{Sequence = 1 , FromDate = new DateTime(2014,1,1),
ToDate = new DateTime(2014,1,31)},
And myDate:
var myDate = new DateTime(2014, 2, 10);
Is your condition satisfied by the first item of your data? No, because ToDate (January 31st) is not greater than or equal to myDate (February 10th). So no items are skipped by SkipWhile. Perhaps you wanted || instead of &&? (It's still not clear what this query is meant to achieve.)

Group by date range , count and sort within each group LINQ

I have a collection of dates stored in my object. This is sample data. In real time, the dates will come from a service call and I will have no idea what dates and how many will be returned:
var ListHeader = new List<ListHeaderData>
{
new ListHeaderData
{
EntryDate = new DateTime(2013, 8, 26)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 11)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 1, 1)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 15)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 17)
},
new ListHeaderData
{
EntryDate = new DateTime(2013, 9, 5)
},
};
I now need to group by date range like so:
Today (1) <- contains the date 9/17/2013 and count of 1
within 2 weeks (3) <- contains dates 9/15,9/11,9/5 and count of 3
More than 2 weeks (2) <- contains dates 8/26, 1/1 and count of 2
this is my LINQ statement which doesn't achieve what I need but i think i'm in the ballpark (be kind if I'm not):
var defaultGroups = from l in ListHeader
group l by l.EntryDate into g
orderby g.Min(x => x.EntryDate)
select new { GroupBy = g };
This groups by individual dates, so I have 6 groups with 1 date in each. How do I group by date range , count and sort within each group?
Introduce array, which contains ranges you want to group by. Here is two ranges - today (zero days) and 14 days (two weeks):
var today = DateTime.Today;
var ranges = new List<int?> { 0, 14 };
Now group your items by range it falls into. If there is no appropriate range (all dates more than two weeks) then default null range value will be used:
var defaultGroups =
from h in ListHeader
let daysFromToday = (int)(today - h.EntryDate).TotalDays
group h by ranges.FirstOrDefault(range => daysFromToday <= range) into g
orderby g.Min(x => x.EntryDate)
select g;
UPDATE: Adding custom ranges for grouping:
var ranges = new List<int?>();
ranges.Add(0); // today
ranges.Add(7*2); // two weeks
ranges.Add(DateTime.Today.Day); // within current month
ranges.Add(DateTime.Today.DayOfYear); // within current year
ranges.Sort();
How about doing this?
Introduce a new property for grouping and group by that.
class ListHeaderData
{
public DateTime EntryDate;
public int DateDifferenceFromToday
{
get
{
TimeSpan difference = DateTime.Today - EntryDate.Date;
if (difference.TotalDays == 0)//today
{
return 1;
}
else if (difference.TotalDays <= 14)//less than 2 weeks
{
return 2;
}
else
{
return 3;//something else
}
}
}
}
Edit: as #servy pointed in comments other developers may confuse of int using a enum will be more readable.
So, modified version of your class would look something like this
class ListHeaderData
{
public DateTime EntryDate;
public DateRange DateDifferenceFromToday
{
get
{
//I think for this version no comments needed names are self explanatory
TimeSpan difference = DateTime.Today - EntryDate.Date;
if (difference.TotalDays == 0)
{
return DateRange.Today;
}
else if (difference.TotalDays <= 14)
{
return DateRange.LessThanTwoWeeks;
}
else
{
return DateRange.MoreThanTwoWeeks;
}
}
}
}
enum DateRange
{
None = 0,
Today = 1,
LessThanTwoWeeks = 2,
MoreThanTwoWeeks = 3
}
and use it like this
var defaultGroups = from l in ListHeader
group l by l.DateDifferenceFromToday into g // <--Note group by DateDifferenceFromToday
orderby g.Min(x => x.EntryDate)
select new { GroupBy = g };
Do you specifically want to achieve the solution in this way? Also do you really want to introduce spurious properties into your class to meet these requirements?
These three lines would achieve your requirements and for large collections willbe more performant.
var todays = listHeader.Where(item => item.EntryDate == DateTime.Today);
var twoWeeks = listHeader.Where(item => item.EntryDate < DateTime.Today.AddDays(-1)
&& item.EntryDate >= DateTime.Today.AddDays(-14));
var later = listHeader.Where(item => item.EntryDate < DateTime.Today.AddDays(-14));
also you then get the flexibility of different groupings without impacting your class.
[Edit: in response to ordering query]
Making use of the Enum supplied above you can apply the Union clause and OrderBy clause Linq extension methods as follows:
var ord = todays.Select(item => new {Group = DateRange.Today, item.EntryDate})
.Union(
twoWeeks.Select(item => new {Group = DateRange.LessThanTwoWeeks, item.EntryDate}))
.Union(
later.Select(item => new {Group = DateRange.MoreThanTwoWeeks, item.EntryDate}))
.OrderBy(item => item.Group);
Note that I'm adding the Grouping via a Linq Select and anonymous class to dynamically push a Group property again not effecting the original class. This produces the following output based on the original post:
Group EntryDate
Today 17/09/2013 00:00:00
LessThanTwoWeeks 11/09/2013 00:00:00
LessThanTwoWeeks 15/09/2013 00:00:00
LessThanTwoWeeks 05/09/2013 00:00:00
MoreThanTwoWeeks 26/08/2013 00:00:00
MoreThanTwoWeeks 01/01/2013 00:00:00
and to get grouped date ranges with count:
var ord = todays.Select(item => new {Group = DateRange.Today, Count=todays.Count()})
.Union(
twoWeeks.Select(item => new {Group = DateRange.LessThanTwoWeeks, Count=twoWeeks.Count()}))
.Union(
later.Select(item => new {Group = DateRange.MoreThanTwoWeeks, Count=later.Count()}))
.OrderBy(item => item.Group);
Output is:
Group Count
Today 1
LessThanTwoWeeks 3
MoreThanTwoWeeks 2
I suppose this depends on how heavily you plan on using this. I had/have a lot of reports to generate so I created a model IncrementDateRange with StartTime, EndTime and TimeIncrement as an enum.
The time increment handler has a lot of switch based functions spits out a list of times between the Start and End range based on hour/day/week/month/quarter/year etc.
Then you get your list of IncrementDateRange and in linq something like either:
TotalsList = times.Select(t => new RetailSalesTotalsListItem()
{
IncrementDateRange = t,
Total = storeSales.Where(s => s.DatePlaced >= t.StartTime && s.DatePlaced <= t.EndTime).Sum(s => s.Subtotal),
})
or
TotalsList = storeSales.GroupBy(g => g.IncrementDateRange.StartTime).Select(gg => new RetailSalesTotalsListItem()
{
IncrementDateRange = times.First(t => t.StartTime == gg.Key),
Total = gg.Sum(rs => rs.Subtotal),
}).ToList(),

Getting number of records by date in LINQ to SQL query even if no results

I have written the following LINQ-to-SQL to enable me to get a total number of records by day.
There are a few modifications I'd like to make to it in order to have it fit my needs. I have made various attempts but haven't been able to get it right.
I'd like to list the last 9 days, even if there were no 'opens' during this time.
I'd also like to group by Day/Month instead of just Day as I'm doing below.
An example of the output I'd love to get would be:
DateString Opens
===================
9/5 0
10/5 0
11/5 3
etc...
public List<OpensOverTimeViewModel> GetOpensOverTimeViewModel(int eId)
{
DateTime lastDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day).AddDays(-9);
DateTime currentDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day).AddDays(1);
var summary = (from p in db.Opens
where (p.ElementId == eId) && (p.Timestamp >= lastDate) && (p.Timestamp <= currentDate)
let k = new
{
Day = p.Timestamp.Day
}
group p by k into t
select new OpensOverTimeViewModel
{
DateString = t.Key.Day,
TotalOpens = t.Count(),
ElementName = ""
}).ToList();
return summary;
}
Any help on how best to tackle this would be much appreciated.
You can create a left join in code after you got the result back from the database
Put the below lines just before your return summary; line.
var allDates = from idx in Enumerable.Range(0, (currentDate - lastDate).Days)
select lastDate.AddDays(idx);
summary = (
from allDate in allDates
join su in summary on allDate.Day equals su.DateString into x
from su in x.DefaultIfEmpty()
select new OpensOverTimeViewModel
{
DateString = allDate.Day,
TotalOpens = su == null ? 0 : su.TotalOpens,
ElementName = ""
}).ToList();
To group by day/month I suggest using the whole date, replacing t.Key.Day with t.Key.Date and changing DateString into a DateTime.

Categories