Adding where and sum to lambda expression - linq

I have the below Linq query:
var qry = from Output in db.Outputs
join ShiftHours in db.ShiftHourses on Output.ShiftHour equals ShiftHours.ShiftHour
join ShiftData in db.ShiftDatas on Output.ShiftID equals ShiftData.ShiftID
where ShiftData.ShiftDate == date && ShiftData.Line == line
select new ProgressData()
{
CPM = ShiftData.CPM,
Target = ShiftData.Target,
CurrentOutput = db.Outputs.Sum(x=>x.Quantity),
PercentOfTarget = (db.Outputs.Sum(x=>x.Quantity) / ShiftData.Target) * 100
};
It is almost doing what I want but as it stands, the CurrentOutput lambda expression is returning the sum of the entire Quantity column of the Output table as I am unsure how to add in a 'Where' clause as well as the sum function (and hence the PercentOfTarget is also incorrect).
The where clause needs to be the same as the first where clause (date and line are parameters passed to the method):
where ShiftData.ShiftDate == date && ShiftData.Line == line
Can anyone help?
EDIT: Clarification of CurrentOutput.
In the 'Output' table there can be multiple records for a given 'ShiftData.ShiftDate' and 'ShiftData.Line' combination so I would like to calculate a sum of the 'Output' table 'Quantity' column values for a specified 'ShiftDate' and 'Line'
EDIT: Further clarification
This is some sample data from the Output table (OutputID is an auto-increment PK):
public class Output
{
[Key]
public int OutputId { get; set; }
public int ShiftID { get; set; }
public int Quantity { get; set; }
public int ShiftHour { get; set; }
public virtual ShiftData ShiftData { get; set; }
}
This is some sample data from the ShiftData table (ShiftID is an auto-increment PK, there will be more than one record for each date as further line numbers are added):
public class ShiftData
{
[Key]
public int ShiftID { get; set; }
public DateTime ShiftDate { get; set; }
public string Line { get; set; }
public int CPM { get; set; }
public double Target { get; set; }
}
So using the above sample data, I am trying to populate a ProgressData object:
public class ProgressData
{
public int CPM { get; set; }
public double Target { get; set; }
public int CurrentOutput { get; set; }
public double PercentOfTarget { get; set; }
}
Based on the sample data, I would expect my ProgressData object created for line 1 on 13/2/2014 to be populated as such:
CPM = 5, Target = 200, CurrentOutput = 120, PercentOfTarget = 60

You can try to do group join for that purpose :
var qry = from ShiftData in db.ShiftDatas
join Output in db.Outputs on ShiftData.ShiftID equals Output.ShiftID
into ShiftGroup
where ShiftData.ShiftDate == date && ShiftData.Line == line
select new ProgressData()
{
CPM = ShiftData.CPM,
Target = ShiftData.Target,
CurrentOutput = ShiftGroup.Sum(x=>x.Quantity),
PercentOfTarget = (ShiftGroup.Sum(x=>x.Quantity) / ShiftData.Target) * 100
};
Another thing, I can't see why you need to do join with ShiftHours here, since none of it's property used in select statement.

Just as #har07 posted I managed to get it working using the below. I am posting this for reference as it does answer the original question but I'm going to try and use #har07's code as it's tidier than mine.
var qry = (from Output in db.Outputs
join ShiftHours in db.ShiftHourses on Output.ShiftHour equals ShiftHours.ShiftHour
join ShiftData in db.ShiftDatas on Output.ShiftID equals ShiftData.ShiftID
where ShiftData.ShiftDate == date && ShiftData.Line == line
select new
{
ShiftData.ShiftDate,
ShiftData.Line,
ShiftData.CPM,
ShiftData.Target,
Output.Quantity
}).ToList();
var progress = qry.GroupBy(l => l.ShiftDate).Select(g => new ProgressData()
{
CPM = g.Where(c => c.ShiftDate == date && c.Line == line).Select(c => c.CPM).FirstOrDefault(),
Target = g.Where(c => c.ShiftDate == date && c.Line == line).Select(c => c.Target).FirstOrDefault(),
CurrentOutput = g.Where(c => c.ShiftDate == date && c.Line == line).Sum(c => c.Quantity),
PercentOfTarget = g.Where(c => c.ShiftDate == date && c.Line == line).Sum(c => (c.Quantity / c.Target) * 100)
});
return progress.FirstOrDefault();

Related

How and where to use AddRange() method

I want to display related data from second table with each value in first table
i have tried this query
public ActionResult Index()
{
List<EmployeeAtt> empWithDate = new List<EmployeeAtt>();
var employeelist = _context.TblEmployee.ToList();
foreach (var employee in employeelist)
{
var employeeAtt = _context.AttendanceTable
.GroupBy(a => a.DateAndTime.Date)
.Select(g => new EmployeeAtt
{
Date = g.Key,
Emp_name = employee.EmployeeName,
InTime = g.Any(e => e.ScanType == "I") ? g.Where(e =>
e.ScanType == "I").Min(e =>
e.DateAndTime.ToShortTimeString())
.ToString() : "Absent",
OutTime = g.Any(e => e.ScanType == "O") ? g.Where(e =>
e.ScanType == "O").Max(e =>
e.DateAndTime.ToShortTimeString())
.ToString() : "Absent"
});
empWithDate.AddRange(employeeAtt);
}
return View(empWithDate);
}
Here is my attendance Table
AttendanceTable
Results
I want to display the shortest time with "I" Column value against each employee and last time with "O" Column value as out time. I think i am not using AddRange() at proper place. Where it should go then?
public partial class TblEmployee
{
public TblEmployee()
{
AttendanceTable = new HashSet<AttendanceTable>();
}
public int EmpId { get; set; }
public string EmployeeName { get; set; }
public virtual ICollection<AttendanceTable> AttendanceTable { get; set; }
}
public partial class AttendanceTable
{
public int Id { get; set; }
public int AttendanceId { get; set; }
public int EmployeeId { get; set; }
public string ScanType { get; set; }
public DateTime DateAndTime { get; set; }
public virtual TblEmployee Employee { get; set; }
}
The actual problem is not related to AddRange(), you need a where clause before GroupBy() to limit attendances (before grouping) to only records related to that specific employee, e.g.
_context.AttendanceTable
.Where(a => a.Employee == employee.EmployeeName)
.GroupBy(a => a.DateAndTime.Date)
...
Depended on your model, it is better to use some kind of ID instead of EmployeeName for comparison if possible.
Also you can use SelectMany() instead of for loop and AddRange() to combine the results into a single list. like this:
List<EmployeeAtt> empWithDate = _context.TblEmployee.ToList()
.SelectMany(employee =>
_context.AttendanceTable
.Where(a => a.Employee == employee.EmployeeName)
.GroupBy(a => a.DateAndTime.Date)
.Select(g => new EmployeeAtt
{
...
})
);
...

Selecting single element from each group that cointains maximum DateTime value

model :
public class ReferenceParameterHistory
{
[Key]
public int IDReferenceParameterHistory { get; set; }
public double Value { get; set; }
public string Value_S { get; set; }
public DateTimeOffset CreatedAt { get; set; }
[Required]
public bool IsStable { get; set; }
public int? IDReference { get; set; }
public Reference Reference { get; set; }
[Required]
public int IDParameterTemplate { get; set; }
public ParameterTemplate ParameterTemplate { get; set; }
}
My code in ASP.NET core controller :
[HttpGet]
public async Task<IActionResult> GetReferenceParameterHistory(int? IDparameterTemplate,
int? IDreference,
DateTimeOffset? startDate,
DateTimeOffset? endDate,
bool latestOnly)
{
try
{
IQueryable<ReferenceParameterHistory> query = _context.ReferenceParameterHistory.OrderByDescending(rph => rph.IDReferenceParameterHistory);
if (IDparameterTemplate != null && IDparameterTemplate > 0)
query = query.Where(rph => rph.IDParameterTemplate == IDparameterTemplate);
if (IDreference != null && IDreference > 0)
query = query.Where(rph => rph.IDReference == IDreference);
if (startDate != null)
query = query.Where(rph=> rph.CreatedAt >= startDate);
if (endDate != null)
query = query.Where(rph => rph.CreatedAt <= endDate);
if (latestOnly)
{
// I tried this but it doesnt compile and I don't have idea how to solve this ....
//query = (from rph in query
// group rph by rph.IDParameterTemplate
// into groups
// where groups.Max(rph => rph.CreatedAt)
// select groups.Key);
}
var referenceParameterHistory = await query.AsNoTracking().ToListAsync();
if (referenceParameterHistory.Any())
return new ObjectResult(referenceParameterHistory);
return new NotFoundResult();
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new StatusCodeResult(500);
}
}
I have a database table based on that ReferenceParameterHistory model class. I want to group records exctracted from that table by IDParameterTemplate and from each group I need to extract records that have the highest value in CreatedAt column (latest records). So each group contains many recods but I need to get only these with max value in CreatedAt column. The result should be IEnumerable of ReferenceParameterHistory since I store that query in an IQueryable variable and then send it to SQL Server to process the query. Commented code in my example is just what I tried but I don't know how to do that.
How can I solve that problem ?
You reused the variable rph inside the lambda.
How to select only the records with the highest date in LINQ
query = (from rph in query
group rph by rph.IDParameterTemplate into g
select g.OrderByDescending(t=>t.CreatedAt).FirstOrDefault());

ASP.Net MVC converting Sql to Linq

I'm updating an old app, to use EF and Linq. I'm having trouble with one of the queries - in SQL it is:
SELECT id, type_id, rule_name, rule_start, rule_end, rule_min
FROM Rules
WHERE (rule_min > 0)
AND (rule_active = 1)
AND (rule_fri = 1)
AND ('2012-01-01' BETWEEN rule_start AND rule_end)
AND (id IN
(SELECT rule_id
FROM RulesApply
WHERE (type_id = 3059)))
ORDER BY pri
So far I have:
var rules = db.Rules.Include("RulesApply")
.Where(t => (t.rule_active == 1)
&& (t.rule_min > 0)
&& (dteFrom >= t.rule_start && dteFrom <= t.rule_end)
&& (this is where I'm stuck)
)
.OrderBy(r => r.pri);
It's the last subquery I'm stuck with adding into the LINQ above:
AND (id IN
(SELECT rule_id
FROM RulesApply
WHERE (type_id = 3059)))
Models are:
public class Rule
{
[Key]
public Int64 id { get; set; }
public Int64 hotel_id { get; set; }
public byte rule_active { get; set; }
public DateTime rule_start { get; set; }
public DateTime rule_end { get; set; }
public int rule_min { get; set; }
public int pri { get; set; }
public virtual ICollection<RuleApply> RulesApply { get; set; }
}
public class RuleApply
{
[Key, Column(Order = 0)]
public Int64 type_id { get; set; }
[Key, Column(Order = 1)]
public Int64 rule_id { get; set; }
[ForeignKey("rule_id")]
public virtual Rule Rule { get; set; }
}
Can anyone please help me complete this query?
Thank you,
Mark
Try doing this:
var rules = db.Rules.Include("RulesApply")
.Where(t => (t.rule_active == 1)
&& (t.rule_min > 0)
&& (dteFrom >= t.rule_start && dteFrom <= t.rule_end)
&& t.RulesApply.Any(a => a.type_id == 3059)
.OrderBy(r => r.pri);
If t.RulesApply is illegal (i.e. doesn't compile), then replace it with the correct reference to the navigation property found on your Rules object that points to the RulesApply object.
If you have set up navigational properties between the entities, you can navigate from one to the other:
//This gets the RulesApply object
var rulesapply = db.RulesApply.Single(x=> x.type_id == 3059);
//This gets all Rules connected to the rulesapply object through its navigational property
var rules = rulesapply.Rules;
//You can use LINQ to further refine what you want
rules = rules.Where( x=> /* and so on...*/ );
You can stack these statements together on a single line, I only split them up for readability purposes :)

Get Count from entity framework

Quite new to EF, basically i want to convert this SQL query:
SELECT
PSKU.ProductSKUID,
PSKU.ProductSKUName,
W.WarehouseID,
W.WarehouseName,
SA.SystemAreaName,
COUNT(SLI.ProductSKUID) AS QTY
FROM dbo.StockLineItem AS SLI INNER JOIN
dbo.ProductSKU AS PSKU ON PSKU.ProductSKUID = SLI.ProductSKUID INNER JOIN
dbo.Warehouse AS W ON W.WarehouseID = SLI.WarehouseID INNER JOIN
dbo.SystemArea AS SA ON SA.SystemAreaID = SLI.SystemAreaID
WHERE (SA.SystemAreaID = 1)
AND W.WarehouseID = #WarehouseID
GROUP BY PSKU.ProductSKUID, PSKU.ProductSKUName, W.WarehouseName, SA.SystemAreaName, W.WarehouseID
To an effective EF statement. This is what i Have so far, my Model class and the method:
[Serializable]
public class StockReturnMethod
{
public int ProductSKUID { get; set; }
public int WarehouseID { get; set; }
public int LotID { get; set; }
public string LotName { get; set; }
public int AreaID { get; set; }
public string AreaName { get; set; }
public int BinID { get; set; }
public string BinName { get; set; }
}
public class DALStockMovement
{
scmEntitiesPrimaryCon entities = new scmEntitiesPrimaryCon();
public List<AvailibleStock> AvailibleStockQty(int warehouseID)
{
var rows = (from PLA in entities.ProductLocationAssignments
from W in entities.Warehouses
from SLI in entities.StockLineItems
from SA in entities.SystemAreas
from PSKU in entities.ProductSKUs
where W.WarehouseID == warehouseID
select new AvailibleStock() { WarehouseID = W.WarehouseID, ProductSKUID = PSKU.ProductSKUID, ProductSKUName = PSKU.ProductSKUName, WarehouseName = W.WarehouseName, Status = SA.SystemAreaName, QtyUnassigned = SLI.ProductSKUID }).ToList();
return rows;
}
Any Advice to get this to an Effective EF Statement would be appreciated
I actually used this tool called Linqer, since I had the SQL
I just popped it into that tool and it generated the Linq for me.
Here is what came out:
var SKUStock = (from sli in entities.StockLineItems
where
sli.SystemArea.SystemAreaID == 1 &&
sli.WarehouseID == warehouseID
group new { sli.ProductSKU, sli.Warehouse, sli.SystemArea, sli } by new
{
ProductSKUID = (System.Int32?)sli.ProductSKU.ProductSKUID,
sli.ProductSKU.ProductSKUName,
sli.Warehouse.WarehouseName,
sli.SystemArea.SystemAreaName,
WarehouseID = (System.Int32?)sli.Warehouse.WarehouseID
} into g
select new AvailibleStock()
{
ProductSKUID = (int)(System.Int32?)g.Key.ProductSKUID,
ProductSKUName = g.Key.ProductSKUName,
WarehouseID = (int)(System.Int32?)g.Key.WarehouseID,
WarehouseName = g.Key.WarehouseName,
Status = g.Key.SystemAreaName,
QtyUnassigned = (int)(Int64?)g.Count(p => p.sli.ProductSKUID != null)
}).ToList();
return SKUStock;
It returns exactly what i need :).

How can I add more than 2 conditions to the LlNQ where clause?

I have a LINQ query with more than 2 where conditions, but it doesn't seem to evaluate with more than 2 conditions. Is there a way to add more conditions to the where clause?
var query =
from f in XElement.Load(MapPath("flightdata3.xml")).Elements("flight")
where (string)f.Element("departurelocation") == From &&
(string)f.Element("destinationlocation") == DestCity &&
(string)f.Element("airline") == Airline
// && (string)f.Element("departuredate") == DepartDate &&
// (string)f.Element("departuretime")==DepartTime
//&& (string)f.Element("returndate")==ReturnDate &&
//(string)f.Element("returntime")==ReturnTime
orderby Convert.ToInt32(f.Element("price").Value)
select new
{
FlightNumber = (Int32)f.Element("flightnumber"),
Airline = (string)f.Element("airline"),
Departure = (string)f.Element("departureairportsymbol"),
DepartTime = (string)f.Element("departuretime"),
Destination = (string)f.Element("destinationairportsymbol"),
ArrivalTime = (string)f.Element("arrivaltime"),
Stops = (int)f.Element("numberofstops"),
Duration = (string)f.Element("duration"),
Cabin = (string)f.Element("cabin"),
Price = "$" + (Int32)f.Element("price"),
ImagePath = (string)f.Element("airlineimageurl").Value
};
LINQ absolutely allows more than two WHERE conditions. Have you tried separating the query into more manageable pieces? LINQ uses deferred execution anyway so you won't see a performance penalty in doing so.
You should also consider making a class to hold the information you're stuffing into the result.
public class FlightDetail
{
public Int32 FlightNumber { get; set; }
public String Airline { get; set; }
public String Departure { get; set; }
public String DepartureTime { get; set; }
public String Destination { get; set; }
public String ArrivalTime { get; set; }
public Int32 Stops { get; set; }
public String Duration { get; set; }
public String Cabin { get; set; }
public Int32 Price { get; set; }
public String ImagePath { get; set; }
}
Then something like this which is more readable but should also help you find whatever bug is popping up.
var flights =
from f in XElement.Load(MapPath("flightdata3.xml")).Elements("flight")
select new FlightDetail
{
FlightNumber = (Int32)f.Element("flightnumber"),
Airline = (string)f.Element("airline"),
Departure = (string)f.Element("departureairportsymbol"),
DepartTime = (string)f.Element("departuretime"),
Destination = (string)f.Element("destinationairportsymbol"),
ArrivalTime = (string)f.Element("arrivaltime"),
Stops = (int)f.Element("numberofstops"),
Duration = (string)f.Element("duration"),
Cabin = (string)f.Element("cabin"),
Price = "$" + (Int32)f.Element("price"),
ImagePath = (string)f.Element("airlineimageurl").Value
};
var flightsByLocation =
flights.
where (string)f.Element("departurelocation") == From &&
(string)f.Element("destinationlocation") == DestCity
select new FlightDetail
{
FlightNumber = (Int32)f.Element("flightnumber"),
Airline = (string)f.Element("airline"),
Departure = (string)f.Element("departureairportsymbol"),
DepartTime = (string)f.Element("departuretime"),
Destination = (string)f.Element("destinationairportsymbol"),
ArrivalTime = (string)f.Element("arrivaltime"),
Stops = (int)f.Element("numberofstops"),
Duration = (string)f.Element("duration"),
Cabin = (string)f.Element("cabin"),
Price = "$" + (Int32)f.Element("price"),
ImagePath = (string)f.Element("airlineimageurl").Value
};
There shouldn't be an issue with having more then one condition. For example, you could have something like this from an Order table.
var orderDetails = (from o in context.OrderDetails
where o.OrderID == orderID
where o.OrderName == orderName
select o).ToList();

Resources