MVC - Count in a foreach loop, best practice - c#

I have been working on getting a count records within a foreach loop. I am going to need run many of these counts on a single page. I am looking for the most efficient way to do this.
I have gotten this far, but I am not sure if I headed down the right path. If I am, how do I get this data into my view.
ViewModel
public class AgtLeadStatsListVM
{
public string LoanAgent { get; set; }
public DateTime LeadDate { get; set; }
public int LeadDailyCt { get; set; }
public int LeadWeeklyCt { get; set; }
public int LeadMTDCt { get; set; }
public int LeadYTDCt { get; set; }
public IEnumerable<MWFUser> AgentList { get; set; }
public virtual WebLead Lead { get; set; }
}
Controller
var model = new AgtLeadStatsListVM();
{
// Get Selected Agent's Information
var AgentList = from l in db.MWFUsers
where (l.UserTitle == "Banker"
select l;
foreach (var agent in AgentList)
{
// Daily Lead Count
var LeadDailyCt = db.WebLeads.Count(x => (x.LoanAgent == agent.UserEmail)
&& (x.LeadDate >= todayDate && x.LeadDate <= todayEndDay));
// Weekly Lead Count
var LeadWeeklyCt = db.WebLeads.Count(x => (x.LoanAgent == agent.UserEmail)
&& x.LeadDate >= firstOfWeek
&& x.LeadDate <= todayEndDay);
// Monthly Lead Count
var LeadMTDCount = db.WebLeads.Count(x => (x.LoanAgent == agent.UserEmail)
&& x.LeadDate >= firstOfMonth
&& x.LeadDate <= todayEndDay);
// YTD Lead Count
var LeadYTDCount = db.WebLeads.Count(x => (x.LoanAgent == agent.UserEmail)
&& x.LeadDate >= firstOfMonth
&& x.LeadDate <= todayEndDay);
}
}
View
#model LoanModule.ViewModels.AgtLeadStatsListVM
<div>
#foreach (var item in Model.AgentList)
{
<p>#Model.LoanAgent</p>
<p>#Model.LeadDailyCt</p>
<p>#Model.LeadWeeklyCt</p>
<p>#Model.LeadMTDCt</p>
<p>#Model.LeadYTDCt</p>
}
I am receiving this error on my View: Object reference not set to an instance of an object. (on line: #foreach (var item in Model.AgentList))
What am I missing?
Thank you.

The semicolon at the end of var model = new AgtLeadStatsListVM(); means that you are no longer in an object initializer after that line. The syntax you're probably trying for is something more along these lines:
var agents =
from l in db.MWFUsers
where l.UserTitle == "Banker"
select l;
var model = new AgtLeadStatsListVM
{
// Get Selected Agent's Information
AgentList = agents.ToList(),
// Daily Lead Count
LeadDailyCt = agents.Sum(a => db.WebLeads.Count(
x => (x.LoanAgent == a.UserEmail)
&& (x.LeadDate >= todayDate && x.LeadDate <= todayEndDay)))
// ...
}
By the way, if you want to get all of this information in a single round-trip, you could use this group by-based trick.
var model =
(from agent in agents
let webLeads = db.WebLeads.Where(x => x.LoanAgent == agent.UserEmail)
group new{agent, webLeads} by 0 into g
select new AgtLeadStatsListVM
{
// Get Selected Agent's Information
AgentList = g.Select(e => e.agent).ToList(),
// Daily Lead Count
LeadDailyCt = g.Sum(e => e.webLeads.Count(x => x.LeadDate >= todayDate && x.LeadDate <= todayEndDay)),
// ...
}).FirstOrDefault();
Update
From your comments it sounds like this is more what you're going for:
var model =
(from agent in agents
let webLeads = db.WebLeads.Where(x => x.LoanAgent == agent.UserEmail)
select new AgtLeadStatsListVM
{
// Get Selected Agent's Information
LoanAgent = agent.UserEmail,
// Daily Lead Count
LeadDailyCt = webLeads.Count(x => x.LeadDate >= todayDate && x.LeadDate <= todayEndDay),
// ...
}).ToList();
And your view code:
#model IEnumerable<LoanModule.ViewModels.AgtLeadStatsListVM>
<div>
#foreach (var item in Model)
{
<p>#item.LoanAgent</p>
<p>#item.LeadDailyCt</p>
<p>#item.LeadWeeklyCt</p>
<p>#item.LeadMTDCt</p>
<p>#item.LeadYTDCt</p>
}
The AgentList property should be removed from your model entirely.

I am receiving this error on my View: Object reference not set to an
instance of an object. (on line: #foreach (var item in
Model.AgentList))
The AgentList is null.
Furthermore, you haven't initialized correctly your model.
Specifically, this line of code
var model = new AgtLeadStatsListVM();
creates a new object of type AgtLeadStatsListVM, where
LoanAgent is null
LeadDate 1/1/0001 12:00:00 AM
LeadDailyCt is 0
LeadWeeklyCt is 0
LeadMTDCt is 0
LeadYTDCt is 0
AgentList is null
WebLead is Lead
The default values, since you didn't set any value. Probably, you want to make use of an object initializer, there you don't need ();. We write just this:
var model = new AgtLeadStatsListVM
{
LoadAgent = "Name of the LoadAgent",
LeadDate = DateTime.Now.Utc,
LeadDailyCt = agents.Sum(a => db.WebLeads.Count(
x => (x.LoanAgent == a.UserEmail)
&& (x.LeadDate >= todayDate && x.LeadDate <= todayEndDay)))
// Do the same for the rest of the corresponding properties.
}

I am going to ignore the error that you are getting (see other answers for it) and reference only best practice and a most efficient way for counting part of the question.
The most efficient way (at least in my opinion) would be using some caching technique for the result and updating the cache on daily basis(since the maximum resolution that you use is daily). Clearly, choosing an appropriate caching mechanism depends on your application. It can go from storing some data in static variable on application start, to running a dedicated Redis server (or any other fast data structure store). The bottom line here is: try to minimize the number of queries to DB and cache any suitable data.

Related

How to get the exact value list result using loop

this is my database which lists 5 results of the menu,
what i want is, if the date not equal to current Date (DateTime.Now), the result will be zero, like this:
Here is my code, on what I'm trying to do,
for (int i = 1; i <= DateTime.Now.ToShortDateString().Length; i++)
if (list.Any(x => x.Date.ToShortDateString() == i.ToString() ))
{
list.ToList().Add(new MenuModel
{
Total = list.First(x => x.Date.ToShortDateString() == i.ToString()).Total,
Location = list.First(x => x.Date.ToShortDateString() == i.ToString()).Location,
});
}
else
{
list.Add(new MenuModel
{
Total = list.First(x => x.Date.ToShortDateString() != i.ToString()).Total=0,
Location = list.First(x => x.Date.ToShortDateString() != i.ToString()).Location,
});
}
but the result that i get is like this,
The location, didnt show the real value, which will be A,B,C,D and E.. How to get the exact value of location?
What you really need is little linq magic. The below linq Get the Total based on the current date to actual value and if not to current date then set to 0.
DateTime date = DateTime.Today;
var menus = list.Select(l => new MenuModel
{
Total = l.Date.Date == date ? l.Total : 0,
Location = l.Location,
});

ASP.Net - Mvc5 : LINQ , Saving duplicate record problem

I am coding daily counter. Database Counter Table is empty. If someone is my first visitor of current day, then I am adding a record to database and i am setting counter=1; After this, when other visitor visit current day, then i am increasing my counter++ and i am updating the record.
So I my records must be like this:
Date:2018-10-01 counter:23
Date:2018-10-02 counter:65
Date:2018-10-03 counter:20
Date:2018-10-04 counter:89
My problem is this: If the site get visitor same time, linq save 2 record for same day. Like this:
Date:2018-10-01 counter:23
Date:2018-10-02 counter:1 //First record: counter=1
Date:2018-10-02 counter:65 //Second record: counter=65
Date:2018-10-03 counter:20
Date:2018-10-04 counter:1 //First record
Date:2018-10-04 counter:89 //second record
Date must be unique. How can I resolve this problem? My code is below. Thanks a lot.
public static int IncreaseCounter_DailySiteVisitors()
{
int counter = 0;
using (var context = new MyProjectEntities())
{
try
{
string format = "dd.MM.yyyy";
DateTime Today = DateTime.Now;
var obj = (from record in context.CounterDailySiteVisitor
where
record.DateRecord != null
&& record.DateRecord.HasValue
&& record.DateRecord.Value.Year == Today.Year
&& record.DateRecord.Value.Month == Today.Month
&& record.DateRecord.Value.Day == Today.Day
select record).FirstOrDefault();
//var obj = context.CounterDailyVisitor.Where(x => x.DateRecord != null && ((DateTime)x.DateRecord).ToString("yyyy.MM.dd") == DateTime.Now.ToString("yyyy.MM.dd")).FirstOrDefault();
if (obj != null)
{
counter = obj.Count ?? 0;
counter++;
obj.Count = counter;
context.SaveChanges();
}
else
{
var newRecordObj = context.CounterDailySiteVisitor.Create();
newRecordObj.Count = 1;
newRecordObj.DateRecord = Today;
context.CounterDailySiteVisitor.Add(newRecordObj);
context.SaveChanges();
}
}
catch (Exception e)
{
}
}
return counter;
}
the chances of this being hit by two thread at the same time is quite low.
but i guess technically it can so you would need to wrap this in a lock
Something like below...
public static int IncreaseCounter_DailySiteVisitors()
{
private readonly object somethingObject = new object();
var context = new MyProjectEntities()
var today = DateTime.Now;
var todaysRecord = context.CounterDailyVisitor
.SingleOrDefault(x => x.DateRecord.Year == Today.Year
&& x.DateRecord.Month == Today.Month
&& x.DateRecord.Day == Today.Day
);
if (todaysRecord != null)
{
//the existing count + 1
todaysRecord.Count = todaysRecord.Count++;
}
else
{
Lock(somethingObject)
{
//recheck
var todaysRecord = context.CounterDailyVisitor
.SingleOrDefault(x => x.DateRecord.Year == Today.Year
&& x.DateRecord.Month == Today.Month
&& x.DateRecord.Day == Today.Day
);
if (todaysRecord != null)
{
//the existing count + 1
todaysRecord.Count = todaysRecord.Count++;
}
else
{
var newRecordObj = new CounterDailyVisitor();
newRecordObj.Count = 1;
newRecordObj.DateRecord = DateTime.Now; //this shouldnt be nullable
context.CounterDailySiteVisitor.Add(newRecordObj);
}
}
}
context.SaveChanges();
}
This is quite a common concurrency problem i.e. race condition. You will either have to Lock around the code that reads and subsequently updates/inserts the value. Or you should call a stored procedure and have all the logic inside the stored proc.
Lock comes with it's own set of issues if you're planning on using a web farm or running multiple instances of this MVC app.

How to determine that a time slot available or not using C# Lambda expression?

Let's say there is a table name ClassRoutineTable:
RoomId DayId StartTime EndTime
1 1 08.00.00 10.00.00
1 1 12.00.00 14.00.00
2 1 10.00.00 12.00.00
Now, I need to check whether the Time slot are available or not for specific room and day. for example, if I do input start time or end time 08.00.00 or 10.00.00 or in between this two time (i.e start time or end time 09.00.00) for the room and day id 1 ;that will return a message that Time slot is not available otherwise it will save.
For Instance I trying something like this:
public string SaveRoom(Allocation allocation)
{
List<Allocation> checckAllocations =
roomAllocationGateway.GetAllAllocations()
.Where(
x =>
x.RoomId == allocation.RoomId && x.DayId == allocation.DayId && "*Code for Check time slot *"
)
.ToList();
if (checckAllocations.Count >0)
{
return "Time Slot Not Available";
}
roomAllocationGateway.SaveRoom(allocation);
return "saved";
}
Hopefully help you
public string SaveRoom(Allocation allocation)
{
List<Allocation> checckAllocations =
roomAllocationGateway.GetAllAllocations()
.Where(
x =>
x.RoomId == allocation.RoomId && x.DayId == allocation.DayId && x.StartTime<=allocation.EndTime && allocation.StartTime<=x.EndTime
)
.ToList();
if (checckAllocations.Count >0)
{
return "Time Slot Not Available";
}
roomAllocationGateway.SaveRoom(allocation);
return "saved";
}
Let 's try below code, Hope it helps you :
public string SaveRoom(Allocation allocation) {
//allocation.StartTime=08.00.00
//allocation.EndTime =10.00.00
List < Allocation > checckAllocations =
roomAllocationGateway.GetAllAllocations()
.Where(
x = >
x.RoomId == allocation.RoomId && x.DayId == allocation.DayId && ((x.startTime <= allocation.StartTime && x.startTime <= allocation.EndTime) || (x.endTime >= allocation.StartTime && x.endTime >= allocation.EndTime))
)
.ToList();
if (checckAllocations.Count > 0) {
return "Time Slot Not Available";
}
roomAllocationGateway.SaveRoom(allocation);
return "saved";
}

EF Duplicated Value

Im getting angry with this error and cannot solve it.
Please, some Jedi master help me.
I'm trying to save trhee Entities: Region, Content and RegionalContent. Region is OK but Regional Content has to be associated with one Content and each Content may have Many RegionalContents(Translations). But I always get a DbUpdateException that has a UpdateException that has a SqlCeException that says something like:
*Impossible to insert a duplicated value with same index. Table name = XBLContents,Constraint name = PK_XBLContents_000000000000001C *
I'm debugging it for some days and could not find the error. Please, note that I'm still a little Padawan.
This is the code that saves the objects in they proper Tables:
Region region;
if (!db.Regions.Any(x => x.ID == Locale))
{
region = new Region { ID = Locale };
db.Regions.Add(region);
db.SaveChanges();
}
else
region = db.Regions.SingleOrDefault(x => x.ID == Locale);
for (int i = start; i < (start + 2); i++)
{
string guid = itens[i].Groups["guid"].Value;
Content c = new Content(guid);
if (!db.Contents.Any(x => x.GUID == guid))
{
c.Type = Type.ToString();
c.PopularInfo(Locale);
db.Contents.Add(c);
}
else
c = db.Contents.SingleOrDefault(x => x.GUID == c.GUID);
RegionalContent regionalcontent;
if (!db.RegionalInfos.Any(x => x.ContentId == guid && x.RegionId == Locale))
{
if (c.HTML == null)
c.PopularInfo(Locale);
regionalcontent = new RegionalContent(c, Locale);
regionalcontent.Region = region;
regionalcontent.Name = HttpUtility.HtmlDecode(itens[i].Groups["name"].Value);
db.RegionalInfos.Add(regionalcontent);
db.Contents.Add(c);
db.SaveChanges();
}
else
regionalcontent = db.RegionalInfos.SingleOrDefault(x => x.ContentId == guid && x.RegionId == Locale);
c.RegionalInfo.Clear();
regionalcontent.Region = region;
c.RegionalInfo.Add(regionalcontent);
Contents.Add(c);
}
You are calling SingleOrDefault when you know 1 already exists. Just use Single.
I would not call SaveChanges to the very end.
Are you sure the GUIDs are unique every time?

Separating Records into indvidual months for mvc

I have a collection of records. Which have two boxers, match date, location etc...
I want to separate them by months and group them together. Currently I have what is below. And it works to a degree. That looks for matchdates in the future. that is this year and steps through each month (1-12) and finds any matches in that date range.
Placing it into a nice dictionary of int, enumerable where int is the month and enumberable is the collection of matches in that month
//Build the matches list by Months!!!
var summarysDic = new Dictionary<int, IEnumerable<MatchSummary>>();
for (int i = 1; i <= 12; i++)
{
var MatchesOfMonth = matches.Where(x => x.MatchDate.Value.Year == DateTime.Now.Year &&
x.MatchDate.Value.Month == i &&
!x.HasResult() &&
x.MatchDate.Value > DateTime.Now);
if (MatchesOfMonth.Count() > 0)
{
summarysDic.Add(i, MatchesOfMonth.OrderBy(x => x.MatchDate).Select(x=> new MatchSummary(x)).ToArray());
}
}
Problem is this currently only deals with this year. I would like to instead make it so it works for "the next 6 months" but this would of course have to work over the new year as well!
Whats the best/cleanest way to go about doing this?
thanks in advance!
P.S on a side note i have yet to find how to simply do DateTime.Now.Month.add(1) for example (as i will always be going from current date forwards!)
-----COMPLETED CODE!-----
//Build the matches list by Months!!!
var summarysDic = new Dictionary<string, IEnumerable<MatchSummary>>();
for (int i = 1; i <= 12; i++)
{
var checkDate = DateTime.Now.AddMonths(i);
var MatchesOfMonth = matches.Where(x => x.MatchDate.Value.Month == checkDate.Month &&
x.MatchDate.Value.Year == checkDate.Year &&
!x.HasResult() &&
x.MatchDate.Value > DateTime.Now);
if (MatchesOfMonth.Count() > 0)
{
var firstMatchDate = MatchesOfMonth.First().MatchDate.Value;
if (firstMatchDate.Year != DateTime.Now.Year)
{
summarysDic.Add(firstMatchDate.ToString("MMMM yyyy"), MatchesOfMonth.OrderBy(x => x.MatchDate).Select(x => new MatchSummary(x)).ToArray());
}
else
{
summarysDic.Add(firstMatchDate.ToString("MMMM"), MatchesOfMonth.OrderBy(x => x.MatchDate).Select(x => new MatchSummary(x)).ToArray());
}
}
}
I believe you can get what you want without modifying your algorithm significantly:
//Build the matches list by Months!!!
var summarysDic = new Dictionary<int, IEnumerable<MatchSummary>>();
for (int i = 0; i <= 6; i++)
{
var checkDate = DateTime.Now.AddMonths(i);
var MatchesOfMonth = matches.Where(x => x.MatchDate.Value.Year == checkDate.Year &&
x.MatchDate.Value.Month == checkDate.Month &&
!x.HasResult() &&
x.MatchDate.Value > DateTime.Now);
if (MatchesOfMonth.Count() > 0)
{
summarysDic.Add(i, MatchesOfMonth.OrderBy(x => x.MatchDate).Select(x=> new MatchSummary(x)).ToArray());
}
}
What's wrong with DateTime.Now.AddMonth(1)?
var MatchesOfMonth = matches.Where(x => x.MatchDate.Value <= DateTime.Now.AddMonth(i)
&& !x.HasResult()
&& x.MatchDate.Value > DateTime.Now);
I haven't compiled that, but it should run with only fairly minor tweeking...

Categories