How to use LINQ group by in ASP.NET MVC? - c#

I have a database:
My query SQL I need to show in my web:
strDateStart = Format(Me.ctlNgayBD.Value, "yyyy/MM/dd")
strDateEnd = Format(Me.ctlNgayKT.Value, "yyyy/MM/dd")
Select Loaive, LoaiXe,
Count(*) As Tongxe,
Sum(Phi) As Tongphi,
phi
From View_XeQuaTramreport As a
Where catruc = " & catruc & " And Convert(varchar(10),NgayCa,111) = '" & strDate & "'
Group by Loaive,Loaixe,phi
Order by Loaive,Loaixe,phi
Select Loaive, LoaiXe,
Count(*) As Tongxe,
Sum(Phi) As Tongphi,
phi
From View_XeQuaTramreport As a
Where Convert(varchar(10),NgayCa,111) >= '" & strDateStart & "'
and Convert(varchar(10),NgayCa,111) <= '" & strDateEnd & "'
Group by Loaive,Loaixe,phi
Order by Loaive,Loaixe,phi"
Image example SQL select:
My controller: Is there a way to use LINQ a instead of string SQL query in controller?
public DongphuocDbContext db = new DongphuocDbContext();
// GET: Report
public ActionResult Index(DateTime? start, DateTime? end )
{
var _context = new DongphuocDbContext();
string mainconn = ConfigurationManager.ConnectionStrings["DongphuocDbContext"].ConnectionString;
SqlConnection sqlconn = new SqlConnection(mainconn);
string sqlquery = "select Loaive,LoaiXe,Count(*) As Tongxe , Sum(Phi) As Tongphi ,Phi" + " from [dbo].[View_XeQuaTramReport] As a " +"where NgayCa between '" + start + "' and '" + end + "'" + " Group by Loaive,Loaixe,phi" + " Order by Loaive,Loaixe,phi";
SqlCommand sqlcomm = new SqlCommand(sqlquery, sqlconn);
sqlconn.Open();
SqlDataAdapter sda = new SqlDataAdapter(sqlcomm);
DataSet ds = new DataSet();
sda.Fill(ds);
List<View_XeQuaTramReport> lc = new List<View_XeQuaTramReport>();
foreach (DataRow dr in ds.Tables[0].Rows)
{
lc.Add(new View_XeQuaTramReport
{
LoaiVe = Convert.ToByte(dr["LoaiVe"]),
LoaiXe = Convert.ToByte(dr["LoaiXe"]),
Phi = Convert.ToDecimal(dr["Phi"])
});
}
sqlconn.Close();
ModelState.Clear();
return View(lc);
}
My view: I want to show 2 tables in roll one view. And add Count"Tongxe" & Sum"TongPhi" but I can't add.
<center>
<p>
#using (Html.BeginForm("Index", "Report", FormMethod.Get))
{
<input type="date" name="start" />
<input type="date" name="end" />
<input type="submit" name="submit" value="Search" />
}
</p>
<table class="table">
<tr>
<th> #Html.DisplayName("LoaiVe") </th>
<th> #Html.DisplayName("LoaiXe") </th>
<th> #Html.DisplayName("Phi") </th>
</tr>
#foreach (var item in Model)
{
<tr>
<td> #Html.DisplayFor(modelItem => item.LoaiVe)</td>
<td> #Html.DisplayFor(modelItem => item.LoaiXe)</td>
<td> #Html.DisplayFor(modelItem => item.Phi)</td>
</tr>
}
</table>
</center>
This is the model configuration of DongphuocDbContext:
modelBuilder.Entity<View_XeQuaTramReport>()
.Property(e => e.Phi)
.HasPrecision(19, 4);
modelBuilder.Entity<View_XeQuaTramReport>()
.Property(e => e.MSNV)
.IsUnicode(false);
modelBuilder.Entity<View_XeQuaTramReport>()
.Property(e => e.maNhanVien)
.IsUnicode(false);
modelBuilder.Entity<View_XeQuaTramReport>()
.Property(e => e.ID)
.IsUnicode(false);
I need to help change string SQL query -> LINQ
I need to add Count"Tongxe" and sum"TongPhi"

Look in your DongphuocDbContext to see if a View_XeQuaTramReport collection is already defined there. If so, the following may do what you need:
DateTime start = ...
DateTime end = ...
var Result = _context.View_XeQuaTramReport
.Where(item => item.NgayCa >= start && item.NgayCa <= end) // Date arithmetic, not text
.GroupBy(item => new {item.Loaive, item.Loaixe, item.Phi}) // Multi-value key
.Select(grp => new {
Loaive = grp.Key.Loaive,
Loaixe = grp.Key.Loaixe,
Tongxe = grp.Count(),
Tongphi = grp.Sum(item => item.Phi),
Phi = grp.Key.Phi
})
.ToList();
A side note regarding your original query: You should never do date comparisons by converting the dates to text (as in Convert(varchar(10),NgayCa,111)). Instead, learn how to parameterize your queries, passing start and end in as proper DATE/DATETIME/DATETIME2 types and perform direct date-to-date comparisons. This is critical for database efficiency, because if you had an index on NgayCa (you likely should), this index would become useless if you do string comparisons instead of date comparisons.

This looks like SQL Server, and the date values in the images look like datetime values. There's no reason to convert dates to strings, even from the date picker controls. In fact, the current SQL query runs far slower than it should and may even return the wrong results due to the string conversions.
The SQL query should look like this, using the date-typed parameters #from and #to
Select Loaive, LoaiXe,
Count(*) As Tongxe,
Sum(Phi) As Tongphi,
phi
From View_XeQuaTramreport As a
Where NgayCa between #from and #to
Group by Loaive,Loaixe,phi
Order by Loaive,Loaixe,phi
EF Core allows creating an equivalent query with LINQ :
public ActionResult Index(DateTime start, DateTime end )
{
using (var context = new DongphuocDbContext())
{
var results=context.View_XeQuaTramReport
.AsNoTracking()
.Where(r=>r.NgayCa >= start.Date &&
r.NgayCa < end.Date.AddDays(1))
.GroupBy(r=>new{r.Loaive,r.Loaixe,r.phi})
.Select(g=>new View_XeQuaTramReport
{
LoaiVe = g.Key.LoaiVe,
LoaiXe = g.Key.LoaiXe,
Phi = g.Key.Phi,
Tongxe = g.Count(),
Tongphi = g.Sum(r=>r.Phi)
})
.OrderBy(r=>new {r.Loaive,r.Loaixe,r.Phi})
.ToList();
return View(results);
}
}
LINQ has no Between clause. Date comparisons are tricky too, if the database field contains a time component. The Where clause in this query will return all rows with a date value greater or equal than the start date (start.Date) and strictly less than the next day after end (end.Date.AddDays(1)). This way, there's no reason to trim r.NgayCa from its date component, if it has one :
.Where(r=>r.NgayCa >= start.Date && r.NgayCa < end.Date.AddDays(1))
AsNoTracking() is used to tell EF to not track the entities it loads. Normally EF will track all the entities it loads so it can detect any changes and generate modification SQL statements when SaveChanges is called. This takes more memory and slows down the query.
Finally, the DbContext is defined in a using block to ensure it's disposed when it's no longer needed. Otherwise the DbContext and the entities it tracks will remain in memory until the garbage collector runs.
Conditional queries
The question's action uses nullable dates. That's only meaningful if we want the same action to search before, after or between dates. In that case both the SQL and LINQ queries would have to be rewritten. None of these would work with a NULL date: Convert(varchar(10),NgayCa,111) >=NULL, Ngayca>= NULL or Ngayca between NULL and ....
In LINQ, the query can be build dynamically :
public ActionResult Index(DateTime? start, DateTime end )
{
var query=context.View_XeQuaTramReport
.AsNoTracking()
.AsQueryable();
if(startDate.HasValue)
{
query=query.Where(r=>r.NgayCa >= start.Value.Date);
}
if(endDate.HasValue)
{
query=query.Where(r=>r.NgayCa < end.Value.Date.AddDays(1));
}
var results=query.GroupBy(r=>new{r.Loaive,r.Loaixe,r.phi})
.Select(g=>new View_XeQuaTramReport {
LoaiVe = g.Key.LoaiVe,
LoaiXe = g.Key.LoaiXe,
Phi = g.Key.Phi,
Tongxe = g.Count(),
Tongphi = g.Sum(r=>r.Phi)
})
.OrderBy(r=>new {r.Loaive,r.Loaixe,r.Phi})
.ToList();

Related

Edit multiple records - asp.net MVC [duplicate]

This question already has answers here:
MVC5 Razor html.dropdownlistfor set selected when value is in array
(3 answers)
Closed 5 years ago.
Been banging my head off the wall for a couple of days with this one and can't seem to figure it out or find an article that answers my question.
I'm working on a rota system for work, which is almost complete, but I'm stuck on this part in the admin area.
It will allow management to edit multiple rota entries for a given date range, for a given team.
It will eventually use a view model to pass in the dateFrom, dateTo, and Team (teamID) - I've just done it this way for now to try to get it working.
The rotas are stored in the Rotas table (see below.)
Rotas table
MorningShift and EveningShift (both FK's) store the employee's payroll number e.g. 123456.
The employee details are stored in the TeamMembers table (see below.)
TeamMembers table
The Username in the TeamMembers table is the employee's payroll number.
My issue is that I can't seem to get the currently entered value in the rotas table to display in the dropdown list in the view. It just has the first employee in the select list (Model.TeamMembers) displayed for all dropdowns.
My question is, how do I get the current value to display in each dropdown in the view?
I did try
#Html.DropDownListFor(m => m.Rotas[i].MorningShift, Model.TeamMembers,
Model.Rotas[i].TeamMember1.FirstName + " " + Model.Rotas[i].TeamMember1.Surname, null)
but while this shows the correct employee, it doesn't have a value set for the option, and obviously shows the employee's name again because that is in the selectlist.
What I have so far:
The controller:
public async Task<ActionResult> ModifyRotas(DateTime? dateFrom,
DateTime? dateTo, int? Team)
{
Auth auth = new Auth();
bool isAdmin = auth.IsAdmin();
if (!isAdmin)
{
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
}
if (dateFrom != null && dateTo != null)
{
DateTime today = DateTime.Today;
DateTime startDate = Convert.ToDateTime(dateFrom);
DateTime endDate = Convert.ToDateTime(dateTo);
// Make sure earlier shifts can't be modified
if (startDate < today)
{
startDate = today;
}
// Verify the team exists
Team teamDetails = await db.Teams.FindAsync(Team);
if (teamDetails == null)
{
return HttpNotFound();
}
ModifyRotaVM vm = new ModifyRotaVM();
// Get rotas for the team for the dates specified
var rotas = await db.Rotas.Where(r => r.TeamID == Team && (r.ShiftDate >= startDate && r.ShiftDate <= endDate)).ToListAsync();
vm.Rotas = rotas;
// Generate the dropdown lists
var teamMembers = db.TeamMembers.Where(m => m.TeamID == Team && m.Deleted == 0).Select(m => new SelectListItem() { Text = m.FirstName + " " + m.Surname, Value = m.Username });
ViewBag.MorningShift = teamMembers;
ViewBag.EveningShift = teamMembers;
vm.TeamMembers = teamMembers;
return View(vm);
}
// If we got here, something went wrong; redisplay the form
return RedirectToAction("Modify");
}
The VM:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace TeamRota.Models
{
public class ModifyRotaVM
{
public List<Rota> Rotas { get; set; }
public IEnumerable<SelectListItem> TeamMembers { get; set; }
}
}
The view:
#model TeamRota.Models.ModifyRotaVM
#{
ViewBag.Title = "ModifyRotas";
}
<h2>Modify Rotas</h2>
#using (Html.BeginForm("RotaMod", "Rotas", new { area = "Admin" },
FormMethod.Post))
{
// Being passed in so we can redirect back to the same page after the update
#Html.Hidden("startDate", Request.QueryString["dateFrom"])
#Html.Hidden("endDate", Request.QueryString["dateFrom"])
#Html.Hidden("TeamID", Request.QueryString["Team"])
<table width="100%">
<tr>
<td>Shift date</td>
<td>Morning shift</td>
<td>Evening shift</td>
</tr>
#for (int i = 0; i < Model.Rotas.Count(); i++)
{
<tr>
<td>
#Html.HiddenFor(m => m.Rotas[i].ID)
#Html.DisplayFor(m => m.Rotas[i].ShiftDate)
</td>
<td>#Html.DropDownListFor(m => m.Rotas[i].MorningShift, Model.TeamMembers, Model.Rotas[i].TeamMember1.FirstName + " " + Model.Rotas[i].TeamMember1.Surname, null)</td>
<td>#Html.DropDownListFor(m => m.Rotas[i].EveningShift, Model.TeamMembers, Model.Rotas[i].TeamMember.FirstName + " " + Model.Rotas[i].TeamMember.Surname, null)</td>
</tr>
}
<tr>
<td colspan="4"><input type="submit" class="btn btn-success" value="Update" /></td>
</tr>
</table>
}
Would really appreciate any help that anyone can offer.
Thanks.
Tom
I think you should try recreating your SelectList for the DropDownListFor.
var teamMembers = db.TeamMembers.Where(m => m.TeamID == Team && m.Deleted == 0).Select(m => new { Text = m.FirstName + " " + m.Surname, Value = m.Username });
ViewBag.TeamMembers = teamMembers
Then in your view, you just have to do this:
#Html.DropDownListFor(m => m.Rotas[i].MorningShift, new SelectList(ViewBag.TeamMembers, "Value", "Text", Model.Rotas[i].TeamMember1.Username), null)
#Html.DropDownListFor(m => m.Rotas[i].EveningShift, new SelectList(ViewBag.TeamMembers, "Value", "Text", Model.Rotas[i].TeamMember.Username), null)

Group By 'MM-dd-yyyy hh-mm'

I have records of scores that I group by the date,pid,bidc and use SUM to aggregate the scores.The original field type is datetime. By grouping by date, I am getting incorrect aggregates because I have multiple sets in a given hour-min for a given day.What is the correct way to handle this and aggregate by 'MM-dd-yyyy hh-mm'
var scores = from ts in _context.Scores
select ts;
List<ScoreAgg> aggScores = scores.GroupBy(p => new { p.create_dt.Date, p.pid, p.bidc }).Select(g => new ScoreAgg()
{
pid = g.Key.pid,
bidc = g.Key.bidc,
score = g.Sum(s => s.weight),
create_dt = g.Key.Date
}).ToList<ScoreAgg>();
You can use DbFunction.CreateDateTime to build date without seconds:
scores.GroupBy(p => new {
Date = DbFunctions.CreateDateTime(p.create_dt.Year, p.create_dt.Month, p.create_dt.Day, p.create_dt.Hour, p.create_dt.Minute, second:null)
p.pid,
p.bidc
})
You should consider making a computed column on the table like
DateOnly AS CONVERT(date, [Date])
GroupBy(p => new { p.create_dt.DateOnly, p.pid, p.bidc })
and index DateOnly column to make aggregate faster. if you just do a convert inside your groupby clause Sql will not use the index defined on Date at all.

LINQ to Entities does not recognize the method

This query I wrote is failing and I am not sure why.
What I'm doing is getting a list of user domain objects, projecting them to a view model while also calculating their ranking as the data will be shown on a leaderboard. This was how I thought of doing the query.
var users = Context.Users.Select(user => new
{
Points = user.UserPoints.Sum(p => p.Point.Value),
User = user
})
.Where(user => user.Points != 0 || user.User.UserId == userId)
.OrderByDescending(user => user.Points)
.Select((model, rank) => new UserScoreModel
{
Points = model.Points,
Country = model.User.Country,
FacebookId = model.User.FacebookUserId,
Name = model.User.FirstName + " " + model.User.LastName,
Position = rank + 1,
UserId = model.User.UserId,
});
return await users.FirstOrDefaultAsync(u => u.UserId == userId);
The exception message
System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[WakeSocial.BusinessProcess.Core.Domain.UserScoreModel] Select[<>f__AnonymousType0`2,UserScoreModel](System.Linq.IQueryable`1[<>f__AnonymousType0`2[System.Int32,WakeSocial.BusinessProcess.Core.Domain.User]], System.Linq.Expressions.Expression`1[System.Func`3[<>f__AnonymousType0`2[System.Int32,WakeSocial.BusinessProcess.Core.Domain.User],System.Int32,WakeSocial.BusinessProcess.Core.Domain.UserScoreModel]])' method, and this method cannot be translated into a store expression.
Unfortunately, EF does not know how to translate the version of Select which takes a lambda with two parameters (the value and the rank).
For your query two possible options are:
If the row set is very small small, you could skip specifying Position in the query, read all UserScoreModels into memory (use ToListAsync), and calculate a value for Position in memory
If the row set is large, you could do something like:
var userPoints = Context.Users.Select(user => new
{
Points = user.UserPoints.Sum(p => p.Point.Value),
User = user
})
.Where(user => user.Points != 0 || user.User.UserId == userId);
var users = userPoints.OrderByDescending(user => user.Points)
.Select(model => new UserScoreModel
{
Points = model.Points,
Country = model.User.Country,
FacebookId = model.User.FacebookUserId,
Name = model.User.FirstName + " " + model.User.LastName,
Position = 1 + userPoints.Count(up => up.Points < model.Points),
UserId = model.User.UserId,
});
Note that this isn't EXACTLY the same as I've written it, because two users with a tied point total won't be arbitrarily assigned different ranks. You could rewrite the logic to break ties on userId or some other measure if you want. This query might not be as nice and clean as you were hoping, but since you are ultimately selecting only one row by userId it hopefully won't be too bad. You could also split out the rank-finding and selection of base info into two separate queries, which might speed things up because each would be simpler.

c# linq syntax slow due to multiple queries in single query

I am wondering if there is a better, more efficient way to re-code the linq syntax below to make the query run faster i.e. with a single call to the database. My database is located remotely which causes this to be quite slow:
var query = (from ticket in dataClassesDataContext.Tickets.Where(TicketsToShow.And(SearchVals))
select new
{
Priority = ticket.TicketPriority.TicketPriorityName,
Ticket = string.Format(TicketFormat, ticket.TicketID),
AssetId = ticket.Asset.Serial,
OpenDate = ticket.CheckedInDate,
OpenFor = CalculateOpenDaysAndHours(ticket.CheckedInDate, ticket.ClosedDate),
Account = ticket.Account.Customer.Name,
Description = ticket.Description.Replace("\n", ", "),
Status = ticket.TicketStatus.TicketStatusName,
Closed = ticket.ClosedDate,
THIS IS THE CAUSE ====>>> Amount = GetOutstandingBalanceForTicket(ticket.TicketID),
Paid = ticket.Paid,
Warranty = ticket.WarrantyRepair,
AssetLocation = GetAssetLocationNameFromID(ticket.Asset.LocationID, AssLocNames)
}).Skip(totalToDisplay * page).Take(totalToDisplay);
if (SortOrder.ToLower().Contains("Asc".ToLower()))
{
query = query.OrderBy(p => p.OpenDate);
}
else
{
query = query.OrderByDescending(p => p.OpenDate);
}//ENDIF
The main cause for the poor performance is the code in the function GetOutstandingBalanceForTicket below which calculates the sum of all items in an invoice and returns this as a total in a string:
public static string GetOutstandingBalanceForTicket(int TicketID)
{
string result = string.Empty;
decimal total = 0;
try
{
using (DataClassesDataContext dataClassesDataContext = new DataClassesDataContext(cDbConnection.GetConnectionString()))
{
var queryCustomerTickets = from ticket in dataClassesDataContext.Tickets
where
(ticket.TicketID == TicketID)
select ticket;
if (queryCustomerTickets != null)
{
foreach (var ticket in queryCustomerTickets)
{
var queryTicketChargeItems = from chargeItem in dataClassesDataContext.ProductChargeItems
where chargeItem.ChargeID == ticket.ChargeID &&
chargeItem.Deleted == null
select chargeItem;
foreach (var chargeItem in queryTicketChargeItems)
{
total += (chargeItem.Qty * chargeItem.Price);
}
}
}
}
}
catch (Exception ex)
{
}
return total.ToString("0.##");
}
Thank you in advance.
As you pointed out this code is quite slow as a query will be required for each ticket.
to eliminate the need for multiple queries you should look at applying an inner join between the ticketsToShow and the tickets entity (on the ticketid), using groupby to provide the sum of the charges for each ticket.
This is well illustrated in the answers to LINQ: Using INNER JOIN, Group and SUM
Ideally you would probably approach it more as an eager loading all at once type of setup. However, I do not think linq2sql supports that (I know EF does). One thing you can do is avoid the nested query though. Since you already have access to the ticket table, perhaps you should just issue a Sum() on it from your select statement. Hard for me to verify if any of this is an improvement so this code is kind of on the fly if you will.
//(from ticket in dataClassesDataContext.Tickets.Where(TicketsToShow.And(SearchVals))
(from ticket in dataClassesDataContext.Tickets
//this would be where you could eager load if possible (not entirely required)
//.Include is an EF method used only as example
/*.Include(t => t.TicketPriority)//eager load required entities
.Include(t => t.Asset)//eager load required entities
.Include(t => t.Account.Customer)//eager load required entities
.Include(t => t.TicketStatus)//eager load required entities
.Include(t => t.ProductChargeItems)//eager load required entities
*/
.Where(TicketsToShow.And(SearchVals))
select new
{
Priority = ticket.TicketPriority.TicketPriorityName,
Ticket = string.Format(TicketFormat, ticket.TicketID),
AssetId = ticket.Asset.Serial,
OpenDate = ticket.CheckedInDate,
OpenFor = CalculateOpenDaysAndHours(ticket.CheckedInDate, ticket.ClosedDate),
Account = ticket.Account.Customer.Name,
Description = ticket.Description.Replace("\n", ", "),
Status = ticket.TicketStatus.TicketStatusName,
Closed = ticket.ClosedDate,
//Use Sum and the foreign relation instead of a nested query
Amount = ticket.ProductChargeItems.Where(pci => pci.Deleted == null).Sum(pci => pci.Qty * pci.Price),
Paid = ticket.Paid,
Warranty = ticket.WarrantyRepair,
AssetLocation = GetAssetLocationNameFromID(ticket.Asset.LocationID, AssLocNames)
}).Skip(totalToDisplay * page).Take(totalToDisplay);
if (SortOrder.ToLower().Contains("Asc".ToLower()))
{
query = query.OrderBy(p => p.OpenDate);
}
else
{
query = query.OrderByDescending(p => p.OpenDate);
}
I think, you can make this query simplier. Somethink like this:
public static string GetOutstandingBalanceForTicket(DataClassesDataContext context, int TicketID)
{
decimal total = 0;
var total = (from ticket in context.Tickets
join chargeItem from context.ProductChargeItems on chargeItem.ChargeID == ticket.ChargeID
where (ticket.TicketID == TicketID && chargeItem.Deleted == null)
select chargeItem).Sum(chargeItem => chargeItem.Qty * chargeItem.Price);
return total.ToString("0.##");
}
/*...*/
Amount = GetOutstandingBalanceForTicket(dataClassesDataContext, ticket.TicketID),
Now, you can inline this methos in your query.
It can contains syntax errors, because I wrote it in notepad.

Speeding up linq group sum queries

I have a query that processes about 500 records pulled from various tables, grouped and then summaried (if that's a word) into a working report. Everything works fine but it takes about 30 seconds to run this one report and i'm getting complaints from my users.
The procedure in question is this one:
public static List<LabourEfficiencies> GetLabourEfficienciesByTimeSheet(DateTime dateFrom, DateTime dateTo)
{
CS3Entities ctx = new CS3Entities();
//get all relevant timesheetline items
var tsItems = from ti in ctx.TimeSheetItems
where ti.TimeSheetHeader.Date >= dateFrom && ti.TimeSheetHeader.Date <= dateTo && ti.TimeSheetHeader.TimeSheetCategory != "NON-PROD"
select new TimesheetLine
{
TimesheetNo = ti.TimeSheetNo,
HoursProduced = ti.HoursProduced,
HoursProducedNet = ti.HoursProducedNet,
ItemID = ti.ItemID,
ProcessID = ti.ProcessID,
ProcessDuration = ti.ProcessDuration,
DowntimeHours = 0M
};
//get all relevant downtimeline items
var tsDownT = from dt in ctx.DowntimeItems
where dt.TimeSheetHeader.Date >= dateFrom && dt.TimeSheetHeader.Date <= dateTo && dt.TimeSheetHeader.TimeSheetCategory != "NON-PROD"
select new TimesheetLine
{
TimesheetNo = dt.TimeSheetNo,
HoursProduced = 0M,
HoursProducedNet = 0M,
ItemID = "",
ProcessID = "",
ProcessDuration = 0M,
DowntimeHours = dt.DowntimeHours
};
//combine them into single table
var tsCombi = tsItems.Concat(tsDownT);
var flatQuery = (from c in tsCombi
join th in ctx.TimeSheetHeaders on c.TimesheetNo equals th.TimeSheetNo
select new
{
th.TimeSheetNo,
th.EmployeeNo,
th.TimeSheetCategory,
th.Date,
c.HoursProduced,
c.ProcessDuration,
th.HoursWorked,
c.HoursProducedNet,
c.DowntimeHours,
c.ItemID
});
//add employee details & group by timesheet no (1 line per timesheet no)
//NB. FnTlHrs checks whether there are any indirect hrs & deducts them if there are
var query = flatQuery.GroupBy(f => f.TimeSheetNo).Select(g => new LabourEfficiencies
{
Eno = g.FirstOrDefault().EmployeeNo,
Dept =g.FirstOrDefault().TimeSheetCategory,
Date = g.FirstOrDefault().Date,
FnGrHrs =g.Where(w =>w.TimeSheetCategory == "FN" &&!w.ItemID.StartsWith("090")).Sum(h => h.HoursProduced),
FnTlHrs =g.Where(w =>w.ItemID.StartsWith("090")).Sum(h => h.ProcessDuration) >0? (g.FirstOrDefault(w =>w.TimeSheetCategory =="FN").HoursWorked) -(g.Where(w =>w.ItemID.StartsWith("090")).Sum(h =>h.ProcessDuration)): g.FirstOrDefault(w =>w.TimeSheetCategory =="FN").HoursWorked,
RmGrHrs =g.Where(w =>w.TimeSheetCategory == "RM").Sum(h => h.HoursProduced),RmGrHrsNet =g.Where(w =>w.TimeSheetCategory == "RM").Sum(h => h.HoursProducedNet),
RmTlHrs =g.FirstOrDefault(w =>w.TimeSheetCategory == "RM").HoursWorked,
MpGrHrs =g.Where(w =>w.TimeSheetCategory =="MATPREP").Sum(h => h.HoursProduced),
MpTlHrs =g.FirstOrDefault(w =>w.TimeSheetCategory =="MATPREP").HoursWorked,
DtHrs = g.Sum(s => s.DowntimeHours),
Indirect =g.Where(w =>w.ItemID.StartsWith("090")).Sum(h => h.ProcessDuration)
});
return query.ToList();
}
The first few bits just gather the data, it's the last query that is the "meat" of the procedure and takes the time.
I'm fairly sure I've done something horrid as the SQL it spits out is terrible, but for the life of me i can't see how to improve it.
Any hints greatly appreciated.
Gordon
Your expression gets optimized both in IQueriable compilation and SQL server query optimization and even here takes that long. It's highly probable that you have no column indexes needed for execution plan to be faster. Copy/paste your rendered SQL expression to SSMS, run it and see the actual plan. Optimize database structire if needed (put indexes). Otherwise, you got that really large amont of data that makes process slow.

Categories

Resources