OrderBy using Dynamic Linq with Relation - c#

I'm trying to order by an attribute of a related object. Just an example, this is the situation. I have this two classes:
public class Publication
{
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Products { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int PublicationId { get; set; }
[ForeignKey("PublicationId")]
public Publication Publication { get; set; }
public int Category { get;set; }
}
Now I need to get all the Publications with Product's Category = 1 and ordered by Publication Id desc:
IQueryable<Publication> query = db.Publications.Where("Products.Any(Category = 1)");
List<Publication> list = query.OrderBy("Id desc").ToList();
This is working great! But... How can I order Publications by Product's category (desc or asc) using Dynamic Linq?

I haven't found how to do it using System.Linq.Dynamic but as for Linq this works
var query = publications.OrderByDescending(p => p.Products.Select(x => x.Category).FirstOrDefault()).ToList();
Here is the test class:
var publications = new List<Publication>
{
new Publication
{
Products = new List<Product>
{
new Product {Category = 5},
new Product {Category = 6},
new Product {Category = 7}
}
},
new Publication
{
Products = new List<Product>
{
new Product {Category = 2},
new Product {Category = 3},
new Product {Category = 4}
}
},
new Publication
{
Products = new List<Product>
{
new Product {Category = 8},
new Product {Category = 9},
new Product {Category = 10}
}
}
};
var query = publications.OrderByDescending(p => p.Products.Select(x => x.Category).FirstOrDefault()).ToList();
query.ForEach(x => Console.WriteLine(x.ToString()));
The output was:
8 , 9 , 10
5 , 6 , 7
2 , 3 , 4
As you see it doesn't internally order the Products.

Related

Filter List inside List Linq

I have list say list of customers and inside each list there is another list of orders
Class Customer
{
int ID,
string Name
List<Order> orders
}
Class Order{
int ID,
string Name
}
Also have a integer list of filteredorderIds = {1,2,3,4}
I want to filter the list of customers who has got orderIds from filteredorderIds list.
So far I am stuck at query like
var filteredCustomers = Customers.Where(x => x.Orders.Any(filteredorderIds.contains(y => y.Id)));
please give credit to #Johnathan Barclay, since he posted faster than i typed example
void Main()
{
var customers = new List<Customer>(){
new Customer(){
ID =1,
Name = "Cust1",
orders = new List<Order>(){
new Order(){ID = 4, Name = "o11"},
new Order(){ID = 5, Name = "o12"},
new Order(){ID = 6, Name = "o13"}
}
},
new Customer(){
ID = 2,
Name = "Cust2",
orders = new List<Order>(){
new Order(){ID = 3, Name = "o21"},
new Order(){ID = 7, Name = "o22"},
new Order(){ID = 8, Name = "o23"}
}
}
};
customers.Where(w =>
w.orders.Any(w => filteredorderIds.Contains(w.ID))
).Dump();
}
List<int> filteredorderIds = new List<int>() { 1, 2, 3, 4 };
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public List<Order> orders { get; set; }
}
public class Order
{
public int ID { get; set; }
public string Name { get; set; }
}

Recursive query in EntityFramework throws NotSupportedException

I want to build a recursive query in EntityFramework 6.2.0 to get an employee and all his "direct" (one level) and all other subordinates down all the hierarchy.
My point was to use List<IQueryable<T>> to build a whole query and then run it just one time with one rount-trip to the database.
Here is my attempt to do it:
private static List<IQueryable<Employee>> queryables = new List<IQueryable<Employee>>();
static void Main(string[] args)
{
using (var db = new EmployeeContext())
{
IQueryable<Employee> managers = db.Employee.Where(x => x.Id == 1);
GetSlaves(managers);
// the System.NotSupportedException occurs in this line
IQueryable<Employee> employees = queryables.Aggregate(Queryable.Union);
// but throws here
var res = (
from e in employees
join d in db.EmployeeDoc on e.Id equals d.EmployeeId
select new { e.Id, e.EmployeeName, d.DocNumber }).ToList();
}
Console.ReadLine();
}
static void GetSlaves(IQueryable<Employee> managers)
{
if (managers != null)
{
queryables.Add(managers);
foreach (var m in managers)
{
Console.WriteLine($"{m.Id} {m.EmployeeName} {m.Position}");
GetSlaves(m.Slaves.AsQueryable());
}
}
}
But I get an System.NotSupportedException: 'Unable to create a constant value of type 'EF6.Employee'. Only primitive types or enumeration types are supported in this context.'
The above C# code was an attempt to replace the following SQL code:
declare #managerId int = 1
;with Employees(Id, EmployeeName)
as
(
select e.Id, e.EmployeeName from Employee as e
where Id = #managerId
union all
select e.Id, e.EmployeeName from Employee as e
inner join Employees as em on e.ManagerId = em.Id
)
select e.Id, e.EmployeeName, d.DocNumber
from Employees e
inner join EmployeeDocuments d on e.Id = d.EmployeeId
UPDATED:
Here is a SQL script that I use:
create table Employee
(
Id int not null identity(1,1) primary key,
EmployeeName varchar(20),
Position varchar(30),
ManagerId int constraint FK_Employee foreign key references Employee(Id)
)
insert into Employee (EmployeeName, Position, ManagerId) values
('John', 'CEO', NULL),
('Marry', 'Head of sales division', 1),
('Mike', 'Head of HR division', 1),
('Jack', 'Sales manager', 2),
('Olivia', 'Sales manager', 2),
('Sophia', 'Sales manager', 2),
('Nadya', 'HR manager', 3),
('Tim', 'HR manager', 3),
('Jim', 'Salesman', 4),
('Sergey', 'Salesman', 4),
('Dmitry', 'Salesman', 5),
('Irina', 'Salesman', 5),
('William', 'Assistant', 8)
select * from Employee
Create table EmployeeDocuments
(
Id int not null identity(1,1) primary key,
DocNumber varchar(20),
EmployeeId int not null constraint FK_Docs_Employee foreign key references Employee(Id)
)
insert into EmployeeDocuments (DocNumber, EmployeeId) values
('1/2019-01-15', 1), ('3/2019-02-25', 3), ('4/2019-01-31', 4), ('9/2019-02-28', 9)
select * from EmployeeDocuments
Here is Employee class:
[Table("Employee")]
public partial class Employee
{
public Employee()
{
Slaves = new HashSet<Employee>();
}
public int Id { get; set; }
[StringLength(20)]
public string EmployeeName { get; set; }
[StringLength(30)]
public string Position { get; set; }
public int? ManagerId { get; set; }
public virtual ICollection<Employee> Slaves { get; set; }
public virtual Employee Manager { get; set; }
public virtual ICollection<EmployeeDoc> EmployeeDocs { get; set; }
}
The error is due to the query being static. Not sure why you need a query when the code is very simple. See below
class Program
{
static void Main(string[] args)
{
Employee.GetSlaves();
}
}
public partial class Employee
{
public static List<Employee> employees = new List<Employee>() {
new Employee() {Id = 1, EmployeeName = "John", Position = "CEO", ManagerId = null},
new Employee() {Id = 2,EmployeeName = "Marry", Position = "Head of sales division", ManagerId = 1},
new Employee() {Id = 3,EmployeeName = "Mike", Position = "Head of HR division", ManagerId = 1},
new Employee() {Id = 4,EmployeeName = "Jack", Position = "Sales manager", ManagerId = 2},
new Employee() {Id = 5,EmployeeName = "Olivia", Position = "Sales manager", ManagerId = 2},
new Employee() {Id = 6,EmployeeName = "Sophia", Position = "Sales manager", ManagerId = 2},
new Employee() {Id = 7,EmployeeName = "Nadya", Position = "HR manager", ManagerId = 3},
new Employee() {Id = 8,EmployeeName = "Tim", Position = "HR manager", ManagerId = 3},
new Employee() {Id = 9,EmployeeName = "Jim", Position = "Salesman", ManagerId = 4},
new Employee() {Id = 10,EmployeeName = "Sergey", Position = "Salesman", ManagerId = 4},
new Employee() {Id = 11,EmployeeName = "Dmitry", Position = "Salesman", ManagerId = 5},
new Employee() {Id = 12,EmployeeName = "Irina", Position = "Salesman", ManagerId = 5},
new Employee() {Id = 13,EmployeeName = "William", Position = "Assistant", ManagerId = 8}
};
public static void GetSlaves()
{
Employee ceo = employees.Where(x => x.ManagerId == null).First();
GetSlavesRecursive(ceo);
}
public int Id { get; set; }
public string EmployeeName { get; set; }
public string Position { get; set; }
public int? ManagerId { get; set; }
public virtual ICollection<Employee> Slaves { get; set; }
public virtual Employee Manager { get; set; }
//public virtual ICollection<EmployeeDoc> EmployeeDocs { get; set; }
static void GetSlavesRecursive(Employee manager)
{
manager.Slaves = employees.Where(x => x.ManagerId == manager.Id).ToList();
foreach (Employee slave in manager.Slaves)
{
GetSlavesRecursive(slave);
}
}
}

Cannot group data in LINQ

I have a question about a LINQ grouping.
I thought that grouping would be a simple matter of using the GroupBy function on the result set and specifying what to group it by. However my items appear to not be grouping together and instead are displaying as if the GroupBy function wasn't there. I want to group by the itemPk, but I'm can't seem to do it. I have tried grouping by both category.ItemFk and Item.Itempk, but no luck. Could someone give me a pointer on this?
var itemIds = items.Select(i => i.ItemId).ToList();
var itemAndCatJoin =
from item in Context.SCS_Items
join category in Context.SCS_ItemCategories
on item.ItemPk equals category.ItemFk
into temp
from category in temp.DefaultIfEmpty()
select new ExportItemTable
{
Category = category,
Item = item
};
return itemAndCatJoin.Where(i => itemIds.Contains(i.Item.ItemPk))
.GroupBy(n => new {n.Item, n.Category})
.Select(i => new ExportableItem
{
ItemPk = i.Key.Item.ItemPk,
Name = i.Key.Item.Name,
Description = i.Key.Item.Description,
Price = i.Key.Item.Price,
Category = i.Key.Category.Category.Category_Name,
GLDepartment = i.Key.Category.GL_Department.Name ?? "",
GLName = i.Key.Category.GL_Name.Name ?? "",
StartDate = i.Key.Item.StartDate,
EndDate = i.Key.Item.EndDate,
FiscalYear = i.Key.Item.SCS_FiscalYear.Name,
School = i.Key.Item.School != null ? i.Key.Item.School.School_Name : i.Key.Item.Board.Board_Name,
Beneficiary = i.Key.Item.SCS_Beneficiary.Name,
Quantity = i.Key.Item.MaxQuantity,
Deleted = i.Key.Item.DeletedFlag,
OptionalStudents = i.Key.Item.SCS_Attachments.Where(a => !a.IsRequired).SelectMany(a => a.SCS_StudentAttachments).Where(s => !s.DeletedFlag).Select(s => s.StudentFk).Distinct().Count(),
RequiredStudents = i.Key.Item.SCS_Attachments.Where(a => a.IsRequired).SelectMany(a => a.SCS_StudentAttachments).Where(s => !s.DeletedFlag).Select(s => s.StudentFk).Distinct().Count(),
IsPublic = i.Key.Item.IsPublic,
AllowRecurring = i.Key.Item.AllowRecurringPayments,
EffectiveCutoff = i.Key.Item.SCS_Attachments.Where(a => !a.DeletedFlag && a.CourseDropCutoff.HasValue).Select(a => a.CourseDropCutoff).OrderBy(a => a).FirstOrDefault(),
CreatedDate = i.Key.Item.CreatedDate
}).OrderBy(i => i.ItemPk).ToList();
}
your groupbyy is indeed doing nothing for you, you need to tell the groupby what to group by....
like
.GroupBy(n => n.Category)
Here is a simple example to your grouping question:
class Program
{
static void Main()
{
var allItems = GetAllItems();
var groups = from item in allItems
group item by item.Category
into newGroup
select newGroup;
foreach (var group in groups)
{
Console.WriteLine($"\nCategory: {group.Key}");
foreach (var item in group)
{
Console.WriteLine($"{item.Name}: {item.Price}");
}
}
Console.ReadLine();
}
static List<Category> GetAllCategories()
{
return new List<Category>()
{
new Category() { Id = 1, Name = "Programming Books" },
new Category() { Id = 2, Name = "Fiction Books" }
};
}
static List<Item> GetAllItems()
{
return new List<Item>()
{
new Item() { Id = 1, Name = "Embedded Linux", Category = 1, Price = 9.9 },
new Item() { Id = 2, Name = "LINQ In Action", Category = 1, Price = 36.19 },
new Item() { Id = 3, Name = "C# 6.0 and the .NET 4.6 Framework", Category = 1, Price = 40.99 },
new Item() { Id = 4, Name = "Thinking in LINQ", Category = 1, Price = 36.99 },
new Item() { Id = 5, Name = "The Book Thief", Category = 2, Price = 7.99 },
new Item() { Id = 6, Name = "All the Light We Cannot See", Category = 2, Price = 16.99 },
new Item() { Id = 7, Name = "The Life We Bury", Category = 2, Price = 8.96 }
};
}
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public int Category { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
This example is simple enough for anyone new to LINQ. I am sure you can make some adjustment to make it work for your specific issue. Hope this will help.

LINQ group list and combine collections

Given these classes:
public class Employee
{
public int EmployeeId { get; set; }
public int GroupId { get; set; }
public List<Plans> Plans { get; set; }
}
public class Plan
{
public int PlanYearId { get; set; }
public string Name { get; set; }
}
And given a setup like so:
var employees = new List<Employee> {
new Employee {
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId = 1111,
Name = "Benefit 1"
}}};
new Employee {
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId= 2222,
Name = "Benefit 2"
},
new Plan {
PlanReferenceId= 2222,
Name = "Benefit 3"
}}}};
How can I use LINQ to group these employees by both EmployeeId and GroupId and then combine the two List<Plan> properties so that i would end up with something like this:
var employee = new Employee
{
EmployeeId = 1,
GroupId = 1,
Plans = new List<Plan> {
new Plan {
PlanReferenceId = 1111,
Name = "Benefit 1"
},
new Plan {
PlanReferenceId = 2222,
Name = "Benefit 2"
},
new Plan {
PlanReferenceId = 2222,
Name = "Benefit 3"
}
}
}
Just use combination of GroupBy and SelectMany:
var result = employees
.GroupBy(e => new { e.EmployeeId, e.GroupId })
.Select(g => new Employee
{
EmployeeId = g.Key.EmployeeId,
GroupId = g.Key.GroupId,
Plans = g.SelectMany(e => e.Plans).ToList()
}).ToList();

C# LINQ Take limited results per grouped

I have list that includes class named 'ID', 'Name' and 'Category'. There are 6 item in list.
List<MyData> list =
{
{0, "John", "Police"},
{1,"Michael", "Police"},
{2,"Alice", "Police"},
{3, "Ferdinand", "Thief"},
{4, "Jocas", "Thief"},
{5, "Connor", "Thief"}
};
I wanna list them with limited quantity per group by 'Category' with LINQ.
Example : I want list 2 item for each 'Cateogory'. Listed should be below :
John Police
Michael Police
Ferdinand Thief
Jocas Thief
Use combination of Take and SelectMany:
var results = list.GroupBy(x => x.Category).SelectMany(g => g.Take(2)).ToList();
I've tested it on following Item class:
public class Item
{
public int ID { get; set; }
public string Name { get; set; }
public string Category { get; set; }
}
And query:
List<Item> list = new List<Item>
{
new Item { ID = 0, Name = "John", Category = "Police"},
new Item { ID = 1, Name = "Michael", Category = "Police"},
new Item { ID = 2, Name = "Alice", Category = "Police"},
new Item { ID = 3, Name = "Ferdinand", Category = "Thief"},
new Item { ID = 4, Name = "Jocas", Category = "Thief"},
new Item { ID = 5, Name = "Connor", Category = "Thief"}
};
var results = list.GroupBy(x => x.Category).SelectMany(g => g.Take(2)).ToList();
Returns 4 elements, right as you want.

Categories