Entity Framework Take N items of child collection - linq

Say I have a Customer entity, and a Sales entity, of 1-to-many relationship.
How could I get all Customers with N number of most recent sales?
var result = Customers.Where(c => c.Sales.Any());
This would return all customers with ALL their sales.
What if I want just 2 sales record from each customer?
P/S: I can do that with query syntax, i'm looking for method syntax solution. I just can't figure out how to chain them together in method syntax form
var result = from cust in context.Customers
select new
{
Customers = cust,
Sales = cust.Sales.OrderBy(s => s.Date).Take(2)
};
This works, but i'm not sure if this is the best way to do it.
EDIT:
OK, it turns out the query syntax that i included here is not working too.
Only the Sales in the anonymous type is effectively reduced to 2 records.
var filtered = result.AsEnumerable().Select(r => r.Customers);
doing this will still result in a list of customers with ALL their sales

You can do a project as described in here
var dbquery = Customers.Select( c => new {
Customer = c,
Sales = c.Sales.OrderBy(s => s.Date)
.Take(2).Select( s => new { s, s.SalesDetails})
});
var customers = dbquery
.AsEnumerable()
.Select(c => c.Customer);

Related

How to write SQL translateable linq code that groups by one property and returns distinct list

I want to change code below to be sql translateable because now i get exception.
Basicallly i want list of customers from certain localisation and there could be more than one customer with the same CustomerNumber so i want to take the one that was most recently added.
In other words - distinct list of customers from localisation where "distinct algorithm" works by taking the most recently added customer if there is conflict.
The code below works only if it is client side. I could move Group By and Select after ToListAsync but i want to avoid taking unnecessary data from database (there is include which includes list that is pretty big for every customer).
var someData = await DbContext.Set<Customer>()
.Where(o => o.Metadata.Localisation == localisation)
.Include(nameof(Customer.SomeLongList))
.GroupBy(x => x.CustomerNumber)
.Select(gr => gr.OrderByDescending(x => x.Metadata.DateAdded).FirstOrDefault())
.ToListAsync();
Short answer:
No way. GroupBy has limitation: after grouping only Key and Aggregation result can be selected. And you are trying to select SomeLongList and full entity Customer.
Best answer:
It can be done by the SQL and ROW_NUMBER Window function but without SomeLongList
Workaround:
It is because it is not effective
var groupingQuery =
from c in DbContext.Set<Customer>()
group c by new { c.CustomerNumber } into g
select new
{
g.Key.CustomerNumber,
DateAdded = g.Max(x => x.DateAdded)
};
var query =
from c in DbContext.Set<Customer>().Include(x => x.SomeLongList)
join g in groupingQuery on new { c.CustomerNumber, c.DateAdded } equals
new { g.CustomerNumber, g.DateAdded }
select c;
var result = await query.ToListAsync();

Having trouble grouping columns in Linq query with multiple joins

I have an MVC ViewModel that I'd like to pass through to a Razor view. In the controller, I've created a database context and joined tables together using Linq. Once summed and grouped, I'm getting an error:
Error CS1061 'decimal' does not contain a definition for 'GroupBy' and no accessible extension method 'GroupBy' accepting a first argument of type 'decimal' could be found (are you missing a using directive or an assembly reference?
I've gone through almost every example on stack overflow and google and couldn't find an example that matched the structure of my query. Also, the MS examples are very trivial and are not of much use.
Here is the action in the controller:
public IHttpActionResult GetEmployeeReleasedAllocatedBonus(int eid)
{
var employeeReleasedAllocatedBonus =
(from br in _context.BonusReleases
join emp in _context.Employees
on new
{
br.EmployeeID,
empID = br.EmployeeID
} equals new
{
emp.EmployeeID,
empID = eid
}
join job in _context.Jobs on br.JobID equals job.JobID
join bonus in _context.Bonus
on new
{
br.JobID,
empID = br.EmployeeID
}
equals new
{
bonus.JobID,
empID = bonus.EmployeeID
}
select new EmployeeAllocatedReleasedBonusViewModel()
{
AllocatedToEmployee = br.Amount, AllocatedPercentage = bonus.Amount * 100
,
JobNumber = job.JobNumber, JobDescription = job.JobDescription
})
.ToList()
.Sum(s => s.AllocatedToEmployee)
.GroupBy(g => new {g.JobNumber, g.JobDescription, g.AllocatedPercentage});
return Ok(employeeReleasedAllocatedBonus);
}
It's worth mentioning that the AllocatedPercentage datatype is a decimal. However, I've tried changing it to string but the error message stays.
Also tried using the group functionality right before .ToList() but that didn't work either.
After ToList() you have a List<EmployeeAllocatedReleasedBonusViewModel>.
In Sum(s => s.AllocatedToEmployee), every s is one EmployeeAllocatedReleasedBonusViewModel. Apparently a EmployeeAllocatedReleasedBonusViewModel has a property AllocatedToEmployee which is probably of type decimal. This can be summed into one decimal.
The result of the Sum (a decimal) is the input of your GroupBy. Does type decimal have a method GroupBy? Of course it doesn't!
Alas you forgot to tell us your requirements. It is difficult to extract them from code that doesn't do what you want.
It seems to me that you have two one-to-many relations:
Employees have zero or more BonusReleases. Every BonusRelease belongs to exactly one Employee using foreign key
Jobs have zero or more BonusReleases. Every BonusRelease belongs to exactly one Job.
Now what do you want: do you want all JobNumbers and JobDescriptions of all Jobs with the total of their AllocatedPercentage? I'm not sure what the Employees do within this query.
Whenever you want items with their sub-items, like Schools with their Students, Customers with their Orders, Orders with their OrderLines, use GroupJoin. If you want it the other way round: Student with the School that he attends, Order with the Customer who placed the Order, use Join.
var result = dbContext.Jobs.GroupJoin(dbContext.BonusReleases,
job => job.Id, // from every Job take the primary key
bonusRelease => bonusReleas.JobId, // from every BonusRelease take the foreign key
// parameter ResultSelector: take every Job with all its BonusReleases to make a new:
(job, bonusReleasesOfThisJob) => new
{
JobNumber = job.JobNumber,
JobDescription = job.JobDescription
// do you want the total of all allocated percentages?
TotalAllocatedPercentages = bonusReleasesOfThisJob
.Select(bonus => bonus.Amount)
.Sum(),
// do something to make it a percentage
// or do you want a sequence of allocated percentages?
TotalAllocatedPercentages = bonusReleasesOfThisJob
.Select(bonus => bonus.Amount)
.ToList(),
});
Or do you want the JobNumber / JobDescription / Total allocated bonus per Employee?
var result = dbContext.Employees.GroupJoin(dbContext.BonusReleases,
employee => employee.Id, // from every Employee take the primary key
bonus => bonus.EmployeeId, // from every BonusRelease take the foreign key
(employee, bonusesOfThisEmployee) => new
{
// Employee properties:
EmployeeId = employee.Id,
EmpoyeeName = employee.Name,
// for the jobs: Join the bonusesOfThisEmployee with the Jobs:
Jobs = dbContext.Jobs.GroupJoin(bonusOfThisEmployee,
job => job.Id,
bonusOfThisEmployee => bonusOfThisEmployee.JobId,
(job, bonusesOfThisJob) => new
{
Number = job.Id,
Description = job.Description,
TotalBonus = bonusOfThisJob.Select(bonus => bonus.Amount).Sum(),
}),
});
Harald's comment was key - after ToList(), I had a list of . Therefore I took a step back and said what if I put the results into an anonymous object first. Then do the group by and then the sum, putting the final result into the view model. It worked. Here is the answer.
var employeeReleasedAllocatedBonus =
(from br in _context.BonusReleases
join emp in _context.Employees
on new
{
br.EmployeeID,
empID = br.EmployeeID
} equals new
{
emp.EmployeeID,
empID = eid
}
join job in _context.Jobs on br.JobID equals job.JobID
join bonus in _context.Bonus
on new
{
br.JobID,
empID = br.EmployeeID
}
equals new
{
bonus.JobID,
empID = bonus.EmployeeID
}
select new
{
AllocatedToEmployee = br.Amount
,AllocatedPercentage = bonus.Amount * 100
,JobNumber = job.JobNumber
,JobDescription = job.JobDescription
})
.GroupBy(g => new {g.JobNumber, g.JobDescription, g.AllocatedPercentage})
.Select(t => new EmployeeAllocatedReleasedBonusViewModel
{
JobNumber = t.Key.JobNumber,
JobDescription = t.Key.JobDescription,
AllocatedPercentage = t.Key.AllocatedPercentage,
AllocatedToEmployee = t.Sum(ae => ae.AllocatedToEmployee)
});

Linq: Count number of times a sub list appear in another list

I guess there must be an easy way, but not finding it. I would like to check whether a list of items, appear (completely or partially) in another list.
For example: Let's say I have people in a department as List 1. Then I have a list of sports with a list of participants in that sport.
Now I want to count, in how many sports does all the people of a department appear.
(I know some tables might not make sense when looking at it from a normalisation angle, but it is easier this way than to try and explain my real tables)
So I have something like this:
var peopleInDepartment = from d in Department_Members
group d by r.DepartmentID into g
select new
{
DepartmentID = g.Key,
TeamMembers = g.Select(r => d.PersonID).ToList()
};
var peopleInTeam = from s in Sports
select new
{
SportID = s.SportID,
PeopleInSport = s.Participants.Select(x => x.PersonID),
NoOfMatches = peopleInDepartment.Contains(s.Participants.Select(x => x.PersonID)).Count()
};
The error here is that peopleInDepartment does not contain a definition for 'Contains'. Think I'm just in need of a new angle to look at this.
As the end result I would like print:
Department 1 : The Department participates in 3 sports
Department 2 : The Department participates in 0 sports
etc.
Judging from the expected result, you should base the query on Department table like the first query. Maybe just include the sports count in the first query like so :
var peopleInDepartment =
from d in Department_Members
group d by r.DepartmentID into g
select new
{
DepartmentID = g.Key,
TeamMembers = g.Select(r => d.PersonID).ToList(),
NumberOfSports = Sports.Count(s => s.Participants
.Any(p => g.Select(r => r.PersonID)
.Contains(p.PersonID)
)
)
};
NumberOfSports should contains count of sports, where any of its participant is listed as member of current department (g.Select(r => r.PersonID).Contains(p.PersonID))).

NotSupportedException for LINQ Queries

I am trying to get a list of a database table called oracleTimeCards whose employee id equals to the employeeID in employees list. Here is what I wrote:
LandornetSQLEntities db = new LandornetSQLEntities();
List<OracleEmployee> employees = db.OracleEmployees.Where(e => e.Office.Contains(officeName) && e.IsActive == true).Distinct().ToList();
var oracleTimeCards = db.OracleTimecards.Where(c => employees.Any(e => c.PersonID == e.PersonID)).ToList();
Anyone has any idea?
I'm going to assume you're using Entity Framework here. You can't embed calls to arbitrary LINQ extension methods inside your predicate, since EF might not know how to translate these to SQL.
Assuming you want to find all the timecards for the employees you found in your first query, you have two options. The simplest is to have a navigation property on your Employee class, named let's say TimeCards, that points to a collection of time card records for the given employee. Here's how that would work:
var oracleTimeCards = employees
.SelectMany(e => e.TimeCards)
.ToList();
If you don't want to do this for whatever reason, you can create an array of employee IDs by evaluating your first query, and use this to filter the second:
var empIDs = employees
.Select(e => e.PersonID)
.ToArray();
var oracleTimeCards = db.OracleTimecards
.Where(tc => empIDs.Contains(tc.PersonID))
.ToList();

What's the LINQ to select the latest item from a number of versioned items?

I've got a class like the following:
public class Invoice
{
public int InvoiceId {get;set;}
public int VersionId {get;set;}
}
Each time an Invoice is modified, the VersionId gets incremented, but the InvoiceId remains the same. So given an IEnumerable<Invoice> which has the following results:
InvoiceId VersionId
1 1
1 2
1 3
2 1
2 2
How can I get just the results:
InvoiceId VersionId
1 3
2 2
I.e. I want just the Invoices from the results which have the latest VersionId. I can easily do this in T-SQL, but cannot for the life of me work out the correct LINQ syntax. I'm using Entity Framework 4 Code First.
Order by the VersionId, group them by InvoiceId, then take the first result of each group. Try this:
var query = list.OrderByDescending(i => i.VersionId)
.GroupBy(i => i.InvoiceId)
.Select(g => g.First());
EDIT: how about this approach using Max?
var query = list.GroupBy(i => i.InvoiceId)
.Select(g => g.Single(i => i.VersionId == g.Max(o => o.VersionId)));
Try using FirstOrDefault or SingleOrDefault in place of Single as well... it would give the same result although Single shows the intention better.
EDIT: I've tested both these queries with LINQ to Entities. They seem to work, so perhaps the issue is something else?
Option 1:
var latestInvoices = invoices.GroupBy(i => i.InvoiceId)
.Select(group => group.OrderByDescending(i => i.VersionId)
.FirstOrDefault());
EDIT: Changed 'Last' to 'FirstOrDefault', LINQ to Entities has issues with the 'Last' query operator.
Option 2:
var invoices = from invoice in dc.Invoices
group invoice by invoice.InvoiceId into invoiceGroup
let maxVersion = invoiceGroup.Max(i => i.VersionId)
from candidate in invoiceGroup
where candidate.VersionId == maxVersion
select candidate;
My version:
var h = from i in Invoices
group i.VersionId by i.InvoiceId into grouping
select new {InvoiceId = grouping.Key, VersionId = grouping.Max()};
Update
As was mentioned by Ahmad in the comments, the above query will return a projection. The version below will return a IQueryable<Invoice>. I use composition to build the query because I think it is more clear.
var maxVersions = from i in Invoices
group i.VersionId by i.InvoiceId into grouping
select new {InvoiceId = grouping.Key,
VersionId = grouping.Max()};
var latestInvoices = from i in Invoices
join m in maxVersions
on new {i.InvoiceId, i.VersionId} equals
new {m.InvoiceId, m.VersionId}
select i;

Resources