Looping LINQ query using do while and display the result - c#

I am working on getting individuals who have not paid for the previous months
public ActionResult Arrears()
{
int month = DateTime.Now.Month;
int year = DateTime.Now.Year;
int i = 4;
do
{
var subscriber = db.Subscriptions
.Where(s => s.SubMonth == month && s.SubYear == year)
.Select(s => s.UserName).Distinct().ToList();
var alls = db.Members.Select(s => s.UserName).ToList();
var notsubs = alls.Except(subscriber).ToList();
List<Subscription> user = new List<Subscription>();
List<Subscription> querie = new List<Subscription>();
notsubs.ForEach(s => user.Add(new Subscription(s)));
month = month - 1;
i = i - 1;
return View(user);
} while (i != 0);
}
It gives me a list of the member who have not subscribed in the current month only and yet I want for the last 4 months

It gives me a list of the member who have not subscribed in the current month only and yet I want for the last 4 months
You have a return statement inside your loop. Since there is no condition around the return statement, you will exit the loop during the first iteration.
The quick fix is to declare the List user variable above the loop and to write the return statement below the loop.
That being said, users who have subscribed during at least one of the last 4 months but not another one will appear in the resulting list. If this is not intended, you might want to consider removing the loop entirely and grabbing all your subscriptions of the last 4 months in one go.
Also note that you will have a bug between January and March of each year because the year of the Subscription and your year variable won't match.
To avoid this, I would suggest not using the month and date as two separate fields but having a single field of type DateTime in your Subscription. You can then directly compare this field to DateTime.UtcNow.AddMonths(-4).
Edit:
With a DateTime field:
public ActionResult Arrears()
{
DateTime minDate = DateTime.UtcNow.AddMonths(-4);
var subscribers = db.Subscriptions
.Where(s => s.SubscriptionDate >= minDate)
.Select(s => s.UserName);
return View(
db.Members.Select(m => m.UserName)
.Where(u => subscribers.All(s => s != u))
.ToList());
}
With a Month and Year Field:
public ActionResult Arrears()
{
DateTime iterationDate = DateTime.UtcNow;
var subscribers =
db.Subscriptions.Where(
s => s.SubMonth == iterationDate.Month
&& s.SubYear == iterationDate.Year);
for(int i=0; i<3; i++)
{
iterationDate = iterationDate.AddMonth(-1);
subscribers = subscribers.Union(
db.Subscriptions.Where(
s => s.SubMonth == iterationDate.Month
&& s.SubYear == iterationDate.Year)
);
}
var subscriberNames = subscribers.Select(s => s.UserName).Distinct();
return View(
db.Members.Select(m => m.UserName)
.Where(u => subscriberNames.All(s => s != u))
.ToList());
}
Note that with the first solution you're looking at exactly the last 4 months (2019-03-10), while with the second solution you're looking at all subscriptions since 2019-03-01.
Note 2: these solutions will give you all members who have no subscriptions at all during the last 4 months. Is this what you need?

Related

LINQ - GroupBy Year/Month

I'm trying to populate graph data with total amount(sum) by a last four months, and visually it would look like this:
I've tried so far to group data by year and by a month, but I'm not sure if it's right approach cuz this doesn't work..
Here is the code:
var testQUERY = await _context.Calculation
.AsNoTracking()
.Where(x => (x.PaymentDate != null && x.PaymentDate > DateTime.UtcNow.AddMonths(-4)))
.GroupBy(x => new { x.PaymentDate.Value.Year, x.PaymentDate.Value.Month}).ToListAsync();
Here's my paymentDate :
And I'm wondering how could I group by month only..
Error I'm facing is next:
Error generated for warning
'Microsoft.EntityFrameworkCore.Query.QueryClientEvaluationWarning: The
LINQ expression 'GroupBy(new <>f__AnonymousType0`2(Year =
Convert([p].PaymentDate, DateTime).Year, Month =
Convert([p].PaymentDate, DateTime).Month), [p])' could not be
translated and will be evaluated locally.'. This exception can be
suppressed or logged by passing event ID
'RelationalEventId.QueryClientEvaluationWarning' to the
'ConfigureWarnings' method in 'DbContext.OnConfiguring' or
'AddDbContext'.
P.S If I better think because I'm using
x.PaymentDate != null && x.PaymentDate > DateTime.UtcNow.AddMonths(-4)
I don't need new anonymous type where I included year also.. but obliviusly I'm trying to group by column which does not exist.. ?
Try using this one. See comments for possible fixes.
var testQUERY = await _context.Calculation
.AsNoTracking()
.Where(x => x.PaymentDate != null)
.Select(x => new { PaymentDate = x.PaymentDate.Value, Row=x }) // pre-select non-null payment date
.Where(x => x.PaymentDate > DateTime.UtcNow.AddMonths(-4)) // this should go after the grouping, as it might include from just part of the month
.GroupBy(x => new { x.PaymentDate.Year, x.PaymentDate.Month})
.Select(grp=> new { grp.Key.Year, grp.Key.Month, Count = grp.Count()) // flatten group and calculate aggregates
.ToListAsync();

Linq query to select the records based on the given months for a year

I am writing linq query and finding it difficult to construct the logic using linq syntax. Basically I need to extract records that match the indexid and filter records based on months in the months array and year field. How do I check months array in the linq query
Here is what I am trying but not sure how to check for x.PriceDate.Month against months array
private Tuple<double?, double?> GetBenchMarkByYear(int year, int benchMark1, int[] months)
{
Tuple<double?, double?> benchMarkReturns;
double[] temp1 = null;
var benchMark1Returns = new double?[] { };
benchMark1Returns = GetViewService<MV_INDEX_PERFORMANCE>()
.Where(x => x.Mtd != null && x.IndexId == benchMark1 && x.PriceDate.Year == year && x.PriceDate.Month ).Select(x => x.Mtd)
.ToArray();
}
}
You can simply use the Any() method.
Which mean your condition will be as following:
benchMark1Returns = GetViewService<MV_INDEX_PERFORMANCE>()
.Where(x => x.Mtd != null && x.IndexId == benchMark1 && x.PriceDate.Year == year && months.Any(m=> m == x.PriceDate.Month))
.Select(x => x.Mtd)
.ToArray();
That if of course the entity x.PriceDate.Month is of type int since months if an array of integers.

Is it possible, and how to refactor this with lambda linq

I'm trying to determine a way where if a name is in the WorkedDataPoint list, and falls on any of the days in totalDaysInDateRange it counts it. The basic premice, is if these two dates match, the person worked that day - I'm trying to get a count of "worked" days. There also may be more than one entry per day per user, and we can only count the one, one for the day so DistinctBy in "morelinq"seemed a good choice...
What I have here should work fine, compiler at work so can't say for sure. But how would I do this with linq lambda expressions to make it more concise - particularly the foreach loop, can that in lambda in this application somehow?
public class WorkedDatapoint
{
public string AssignerName { get; set; }
public DateTime Date { get; set; }
public bool AssignedToOther { get; set; }
}
List<DateTime> totalDaysInDateRange
List<WorkedDataPoint> AssignersList
testtech = "Bobby"
//The two lists are loaded here
int totalDaysWorked = 0;
foreach (var day in totalDaysInDateRange)
{
if (AssignersList.Where(d => testtech.Contains(d.AssignerName)).DistinctBy(d => d.Date.Date).Any(d => d.Date.Date == day.Date)
{
totalDaysWorked++;
}
}
Edit: I think I got it but can't test til tomorrow. Any way to declare that variable TotalDaysWorked inline or is that asking too much?
int totalDaysWorked = 0;
totalDaysInDateRange.ForEach((day) =>
{
if (AssignersList
.Where(d => testtech.Contains(d.AssignerName))
.DistinctBy(d => d.Date.Date)
.Any(d => d.Date.Date == day.Date))
{ totalDaysWorked++; }});
This method is "crunching" thousands of records, so thoughts on optimization for speed (plenty of RAM and Intel-I7) would be much appreciated as well.
Can do something like this:
int totalDaysWorked = totalDaysInDateRange.Intersect(AssignersList.Select(b => b.Date)).Count();
As Except uses Set internally:
int count = totalDaysInDateRange.Count() - totalDaysInDateRange.Except(AssignersList.Select(b => b.Date)).Count();
Warning:
For large numbers, linq is incredibly slow and should be banned. More about this here:
I recommend to join totalDaysInDateRange and AssignersList on Date. Then Group by AssignerName and Date. you will get the dates
var jointList = totalDaysInDateRange.Join(
AssignersList,
p => p,
c => c.Date,
(p, c) => new { c.AssignerName, c.Date }
).Distinct().GroupBy(x => x.AssignerName).Select(group =>
new {
AssignerName = group.Key,
Count = group.Count()
}).ToList();
You can write linq query for your problem like:-
var AssignerDaysCount = assignerList.Where(filter =>
totalDaysInDateRange.Contains(filter.Date.Date))
.GroupBy(item => item.AssignerName)
.Select(lst => new { AssignerName = lst.Key, DaysCount = lst.Select(cnt => cnt.Date).Distinct().Count() })
.ToList();

Displaying events by Month with Lambda

I am displaying a list of events by day from SQL database using the code:
IEnumerable<EventInstance> distinctDate = instances
.GroupBy(e => e.StartDate.Date)
.Select(group => group.First());
foreach (var instance in distinctDate)
{
// code to display here
}
What I am trying to do is divide them up by month on the page, so in between each month going down the page, i can put the Month Heading in a H3 tag
If you want one event for each day then grouped by month I think this is what you are looking for. Note you have to also group by year to avoid lumping dates from different years together. I also added a order by in there to make sure the events are in order by date.
var distinctDate = instances
.GroupBy(e => e.StartDate.Date)
.Select(grp => grp.First())
.OrderyBy(e => e.StartDate.Date)
.GroupBy(e => new { e.StartDate.Year, e.StartDate.Month });
Then you would do something like this
foreach(var monthGrouping in distinctDate)
{
var month = monthGrouping.Key.Month;
foreach(var singleDayEvent in monthGrouping)
{
var date = singleDayEvent.StartDate;
}
}

Select object and add to list linq c#

probably a simple Q but still a beginner at this and not sure on how to....
Each WorkStation can have a number of Invoices So my below code will....
store all the workStations, go Through each workStation,
get the last (most recent) invoice for that workStation,
If the invoices date (for the most recent Invoice) is < 12 Months
Add it to the list of sites...
EDIT:
Thanks for all the help guys but I am trying to do it through c# and avoid the LINQ searche you guys have mentioned...thanks for everyone who replied...
My new problem being i need to sort the ChosenInvoices list into asceding order and return the first...as i think it is selecting anyone in the list:
var allWorkSites =
(from worksites in db.Work_Sites
select worksites).Distinct().ToList();
List<Object> chosenInvoices = new List<Object>();
foreach (Work_Site worksite in allWorksites)
{
Invoice lastInvoice = worksite.Invoices.LastOrDefault();
if (lastInvoice != null)
{
if (lastInvoice.Invoice_Date < DateTime.Now.AddMonths(-12))
{
chosenInvoices.Add(workstation);
}
}
}
List<invoice> actualInvoices = db.Work_Stations.Distinct()
.Where(w => w.Invoices.Last().Invoice_Date < DateTime.Now.AddMonths(-12)).Select(w => w.Invoices.Last()).ToList();
This code will return you the list of most recent invoices from each workstation.
To sort your invoice in asscending order list have a method OrderBy(), so order it with this method and then take the first one.
Also list have Sort() method.
allWorkStations
.Where(w => w.Invoices.Last().Invoice_Date < DateTime.Now.AddMonths(-12))
.Select(w => list.add(w));
Or better yet:
List<Work_Station> list = db.Work_Stations
.Distinct()
.Where(w => w.Invoices.Last().Invoice_Date < DateTime.Now.AddMonths(-12))
.ToList();
var allWorkStations =
(from workstation in db.Work_Stations
where workstation.Invoices.Last().Invoice_Date < DateTime.Now.AddMonths(-12)
select workstation).Distinct();
The following code will create list of workstations which had invoice in last year
var checkDate = DateTime.Now.AddMonths(-12);
var resultList = db.Work_Stations
.Distinct()
.Select(ws => new {Ws = ws, Li = ws.Invoices.OrderBy(i => i.Invoice_Date).LastOrDefault()})
.Where(item => item.Li != null && Li.Invoice_Date < checkDate)
.Select(item => item.Ws)
.ToList();
This will work even if the invoices are not in date order:
invoiceLst.AddRange(allWorkStations
.Where(w => w.Invoices.Max(i => i.Invoice_Date) < DateTime.Now.AddMonths(-12)));

Categories

Resources