Selecting single element from each group that cointains maximum DateTime value - linq

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());

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
{
...
})
);
...

Dynamic LINQ: Comparing Nested Data With Parent Property

I've a class with following structure:
public class BestWayContext
{
public Preference Preference { get; set; }
public DateTime DueDate { get; set; }
public List<ServiceRate> ServiceRate { get; set; }
}
public class ServiceRate
{
public int Id { get; set; }
public string Carrier { get; set; }
public string Service { get; set; }
public decimal Rate { get; set; }
public DateTime DeliveryDate { get; set; }
}
and I've dynamic linq expression string
"Preference != null && ServiceRate.Any(Carrier == Preference.Carrier)"
and I want to convert above string in Dynamic LINQ as follows:
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda<BestWayContext, bool>(condition, null).Compile();
But it showing following error:
Please correct me what am I doing wrong?
It looks like you wanted to do something like this:
var bwc = new BestWayContext
{
Preference = new Preference { Carrier = "test" },
DueDate = DateTime.Now,
ServiceRate = new List<ServiceRate>
{
new ServiceRate
{
Carrier = "test",
DeliveryDate = DateTime.Now,
Id = 2,
Rate = 100,
Service = "testService"
}
}
};
string condition = "Preference != null && ServiceRate.Any(Carrier == #0)";
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda<BestWayContext, bool>(condition, bwc.Preference.Carrier).Compile();
bool res = expression(bwc); // true
bwc.ServiceRate.First().Carrier = "test1"; // just for testing this -> there is only one so I've used first
res = expression(bwc); // false
You want to use Preference which belong to BestWayContext but you didn't tell the compiler about that. If i write your expression on Linq i will do as follows:
[List of BestWayContext].Where(f => f.Preference != null && f.ServiceRate.Where(g => g.Carrier == f.Preference.Carrier)
);
As you see i specified to use Preference of BestWayContext.

Adding where and sum to lambda expression

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();

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 :)

Append OR subquery in Linq

I am trying to build a simple search against some entities (EF4, if that makes any difference). Passed into my search query is a list of criteria objects. The crieteria object looks like this:
public class ClaimSearchCirtieria
{
public Guid? FinancialYear { get; set; }
public bool AllClaimants { get; set; }
public IList<Guid> ClaimantIds { get; set; }
public bool AllExpenseCategories { get; set; }
public IList<ExpenseCategoryAndTypeCriteria> EpenseCategoryAndTypes { get; set; }
}
And the ExpenseCategoryAndTypeCriteria
public class ExpenseCategoryAndTypeCriteria
{
public Guid ExpenseCategory { get; set; }
public bool AllTypesInCatgeory { get; set; }
public IList<Guid> ExpenseTypes { get; set; }
}
Searching on financial years and claimants needs to be an AND query, then I need the expense categories and expense types to be appended as an OR sub query.
In essence I'm trying to do:
select *
from claims
where <financial year> AND <Claimants> AND (expense type 1 OR expense type 2 or expense category X)
So far I've got this:
public PagedSearchResult<Claim> Search(ClaimSearchCirtieria searchCriteria, int page, int pageSize)
{
var query = All();
if (searchCriteria.FinancialYear.HasValue)
{
query = from claim in query
where claim.FinancialYearId == searchCriteria.FinancialYear
select claim;
}
if (!searchCriteria.AllClaimants)
{
query = from claim in query
where searchCriteria.ClaimantIds.Contains(claim.ClaimantId)
select claim;
}
if (!searchCriteria.AllExpenseCategories)
{
foreach (var item in searchCriteria.EpenseCategoryAndTypes)
{
if (item.AllTypesInCatgeory)
{
//Just search on the category
query = query.Where(claim =>
(from transaction in claim.ClaimTransactions
where item.ExpenseCategory == transaction.ExpenseType.ExpenseCategoryId
select transaction).Count() > 0
);
}
else
{
//Search for the specified types
query = query.Where(claim =>
(from transaction in claim.ClaimTransactions
where item.ExpenseTypes.Contains(transaction.ExpenseTypeId)
select transaction).Count() > 0
);
}
}
}
return PagedSearchResult<Claim>.Build(query, pageSize, page);
}
What I'm currently seeing is that the last expense category requested is the only expense category I get results for. Also, looking at the code, it looks like I would expect this to be building a series of AND queries, rather that the required OR.
Any pointers?
You can do this with LINQKit's PredicateBuilder. You need to use AsExpandable() when composing Entity Framework queries.

Resources