Group By using Linq - c#

I need to present a list of Ads grouped by category, including the Ads Count for each category.
Categories are grouped by a Parent Category like Cars that include the Categories Saloon, Cabriolet and Sports.
Models:
public class Ad
{
[Key]
public int Id { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int Id { get; set; }
[ForeignKey("CategoryParent")]
public int? CategoryParent_Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Ad> Ads { get; set; }
}
The result as to be:
Cars - Count: 100 (where 100 is the sum of for example 20 Saloon's Ads, 80 Cabrilet's)
At the moment, I'm only able to present the list of all Categories, and not grouped by Parent Category.
var adIds = {1,2,4,5}
var result =
from c in categoryQuery
let searchCount = c.Ads.Count(a => adIds.Contains(a.Id))
where searchCount > 0
select new CategoryGetAllBySearchDto
{
Id = c.CategoryParent_Id,
Name = c.CategoryParent.Name,
SearchCount = searchCount,
Ids = c.Ads.Where(a => adIds.Contains(a.Id)).Select(a => a.Id)
};

GroupBy in memory:
var adIds = { 1, 2, 4, 5 };
var result = categoryQuery.Where(c => c.Ads.Any(a => adIds.Contains(a.Id)))
.Select(c => new
{
c.CategoryParent_Id,
c.CategoryParent.Name,
Ids = c.Ads.Where(a => adIds.Contains(a.Id)).Select(a => a.Id).AsEnumerable()
})
.ToList()
.GroupBy(c => new {c.CategoryParent_Id, c.Name})
.Select(g => new CategoryGetAllBySearchDto
{
Id = g.Key.CategoryParent_Id,
Name = g.Key.Name,
Ids = g.SelectMany(u => u.Ids).AsEnumerable()
})
.ToList();

i think you need this:
var adIds = { 1, 2, 4, 5 };
var result = from c in categoryQuery
where c.Ads.Any(a => adIds.Contains(a.Id))
group c by new {c.CategoryParent_Id, c.CategoryParent.Name} into g
select new CategoryGetAllBySearchDto
{
Id = g.Key.CategoryParent_Id,
Name = g.Key.Name,
SearchCount = g.SelectMany(u => u.Ads)
.Where(a => adIds.Contains(a.Id))
.Count(),
Ids = g.SelectMany(u => u.Ads)
.Where(a => adIds.Contains(a.Id))
.Select(a => a.Id)
};

you can get out the SearchCount an add the AsEnumerable to Ids to get query just once
public class CategoryGetAllBySearchDto
{
public int? Id { get; set; }
public string Name { get; set; }
public int SearchCount { get { return this.Ids.Count() } }
public IEnumerable<int> Ids { get; set; }
}
and the query :
var adIds = { 1, 2, 4, 5 };
var result = from c in categoryQuery
where c.Ads.Any(a => adIds.Contains(a.Id))
group c by new {c.CategoryParent_Id, c.CategoryParent.Name} into g
select new CategoryGetAllBySearchDto
{
Id = g.Key.CategoryParent_Id,
Name = g.Key.Name,
Ids = g.SelectMany(u => u.Ads)
.Where(a => adIds.Contains(a.Id))
.Select(a => a.Id)
.AsEnumerable()
};

Related

How to select students with repeated low scores?

Scores are considered low if they are less than or equal to 5. I want to select students with repeated low scores.
The expected result is:
Andy
Bobby
Cindy
As each of them has repeated low scores.
Question
I got stuck in completing the last expression GroupBy in the Where clause.
Could you make it done?
class Student
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public List<int> Scores { get; set; } = new List<int>();
public static List<Student> GetStudents()
{
return new List<Student>()
{
new Student
{
Id = 1,
Name="Andy",
Scores={1,1,2,2,3,4,5,6,7,8}
},
new Student
{
Id = 2,
Name="Bobby",
Scores={3,3,3,3,4,5}
},
new Student
{
Id = 3,
Name="Cindy",
Scores={1,1,2,2,3,4,5}
},
new Student
{
Id = 4,
Name="Dave",
Scores={1,2,3,4,5,6,7,8,9,10}
}
};
}
}
class Program
{
static void Main()
{
var query = Student.GetStudents()
.Where(s => s.Scores.GroupBy(i => i).????);
foreach (var x in query)
Console.WriteLine(x.Name);
Console.ReadLine();
}
}
I'd do something like this:
var query = Student.GetStudents()
.Where(s => s.Scores
.Where(x => x <= 5)
.GroupBy(i => i)
.Any(x => x.Count() > 1));
Try following :
var query = Student.GetStudents()
.Select(x => new { student = x.Name, scores = x.Scores.GroupBy(y => y).Select(y => new { score = y.Key, count = y.Count() }).ToList() }).ToList();
var lowScore = query.Where(x => x.scores.Any(y => (y.count > 1) && (y.score <= 5))).ToList();

How can i transfer this to LAMBDA?

Hell sir/mam
this is my raw query
SELECT
dbo.Products.AHPPartnerId,
dbo.Products.Name AS Product,
dbo.AHPPartners.Name AS Partner,
Count(dbo.OrderProducts.ProductId) AS totalCount,
dbo.Products.Id AS ProductId
FROM
dbo.AHPPartners
RIGHT JOIN dbo.Products ON dbo.Products.AHPPartnerId = dbo.AHPPartners.Id
RIGHT JOIN dbo.OrderProducts ON dbo.OrderProducts.ProductId = dbo.Products.Id
GROUP BY
dbo.AHPPartners.Id,
dbo.Products.AHPPartnerId,
dbo.Products.Name,
dbo.AHPPartners.Name,
dbo.Products.Id
ORDER BY
totalCount DESC
how could i transfer this to lambda expression i wanna target it here in my DTO?
{
public long ProductId { get; set; }
public long? PartnerId { get; set; }
public string PartnerName { get; set; }
public string ProductName { get; set; }
public double TotalCount { get; set; }
}
Hello I got an answer already.
var topseller = await Context.Entities.Products
.GroupJoin(Context.Entities.AHPPartners,
products => products.AHPPartnerId,
ahp => ahp.Id,
(products, ahp) => ahp.Select(s => new { p = products, a = s }).DefaultIfEmpty(new { p = products, a = (AHPPartner)null })
).SelectMany(g => g)
.Join(Context.Entities.OrderProducts,
firstJoin => firstJoin.p.Id,
orderProducts => orderProducts.ProductId,
(firstJoin, orderProducts) => new { firstJoin.p, firstJoin.a, orderProducts }
)
.Select(s => new {
ProductName = s.p.Name,
ProductId = s.p.Id,
PartnerName = s.a.Name,
PartnerId = s.a.Id
})
.GroupBy(g => new { g.PartnerName, g.ProductName, g.ProductId, g.PartnerId })
.Select(s => new TopSellerProductDTO {
ProductName = s.Key.ProductName,
PartnerName = s.Key.PartnerName,
PartnerId = s.Key.PartnerId,
ProductId = s.Key.ProductId,
TotalCount = s.LongCount()
})
.ToListAsync();

query linq about 2 lists group by

I have the following query in linq, which takes 2 lists as a data source. The first contains a list of ProductID and its description
public class Venta
{
public string ProductoId { get; set; }
public string clienteRut { get; set; }
}
public class Ventas
{
public List<Venta> lstVentas { get; set; }
}
and the other list has the products sold
public class Productos
{
public List<Producto> lstProductos { get; set; }
}
public class Producto
{
public string id { get; set; }
public string name { get; set; }
}
I need to consult the 5 most sold products, ordered by quantity from the most sold, to the least sold.
So far I have the following linq query, but I do not know how to do it so that I am given the list of the first 5, ordered from highest to lowest based on the quantity (cont)
Venta vta1 = new Venta();
vta1.ProductoId = "1";
vta1.clienteRut = "121370654";
Venta vta2 = new Venta();
vta2.ProductoId = "2";
vta2.clienteRut = "121370654";
Venta vta3 = new Venta();
vta3.ProductoId = "3";
vta3.clienteRut = "121370654";
List<Venta> lstVentasDia = new List<Venta>();
lstVentasDia.Add(vta1);
lstVentasDia.Add(vta2);
lstVentasDia.Add(vta3);
VentasDia vtas = new VentasDia();
vtas.date = "2018-05-01";
vtas.lstVentas = lstVentasDia;
var Lista5Top = from vendidos in vtas.lstVentas
orderby vendidos.ProductoId
group vendidos by vendidos.ProductoId into Grupo
select new { key = Grupo.Key, cont = Grupo.Count() };
I need in addition to that group of result, add the name of the product that is in the list Products, and order it by quantity sold of greater to less only the first 5
Thankful in advance
Gloria
Try following :
Productos productos = new Productos();
var Lista5Top = (from vendidos in vtas.lstVentas
join prod in productos.lstProductos on vendidos.ProductoId equals prod.id
select new { id = vendidos.ProductoId, rut = vendidos.clienteRut, name = prod.name })
.OrderBy(x => x.id)
.GroupBy(x => x.id)
.Select(x => new { id = x.Key, cont = x.Count(), name = x.FirstOrDefault().name })
.OrderByDescending(x => x.cont)
.Take(5).ToList();

Selecting specific columns from GroupBy list

Model:
public class Ticket {
public Ticket();
public int Id { get; set; }
public virtual TicketUrgency TicketUrgency { get; set; }
public int UrgencyId { get; set; }
}
public class TicketUrgency {
public TicketUrgency();
[Key]
public int Id { get; set; }
[MaxLength(50)]
[Required]
public string Name { get; set; }
public ICollection<Ticket> Tickets { get; set; }
}
I have the following linq statement:
var model = Entities
.Include(x => x.TicketUrgency)
.GroupBy(x => x.UrgencyId)
.Select(g => new {
id = g.Key,
count = g.Count(),
name = g.FirstOrDefault(u => u.UrgencyId == g.Key).TicketUrgency.Name
});
I want to Group Entities by UrgencyId and then return the Key (UrgencyId), and also count of the items in a single group and show the name of the Urgency.
When I run it, the query just gets hung up without any exceptions.
This should work, doing it the other way around, by retrieving all TicketUrgencies first and grouping it.
Entities.Include(e => e.Tickets)
.GroupBy(t => t.Id)
.Select(g => new {
id = g.Key,
name = g.FirstOrDefault().Name,
count = g.FirstOrDefault().Tickets.Count()
});
Very simple. Just try this :
var model = Entities
.Include(x => x.TicketUrgency)
.GroupBy(x => new {UrgencyId = x.UrgencyId ,
Name = x.TicketUrgency.Name})
.Select(x=> new { UrgencyId = x.Key.UrgencyId,
Name = x.Key.Name,
Count = x.Count()});
Since you are grouping by UrgencyId, you know all members of g have the same id as the Key, so to pick up the name just pull the first one. You also know g isn't empty because that wouldn't make a group:
var model = Entities
.Include(x => x.TicketUrgency)
.GroupBy(x => x.UrgencyId)
.Select(g => new {
id = g.Key,
name = g.First().TicketUrgency.Name
count = g.Count(),
});
You could group by those two properties:
var model = Entities
.Include(x => x.TicketUrgency)
.GroupBy(x => new{ x.UrgencyId, x.TicketUrgency.Name })
.Select(g => new {
id = g.Key.UrgencyId,
count = g.Count(),
name = g.Key.Name
});
Another way could be, as #ASpirin suggested,starting the query from TickerUrgency:
var result= TicketUrgencies.Include(t=>t.Tickets)
.Where(t=>t.Tickets.Any())
.Select(t=> new {id=t.Id,name=t.Name, count= t.Tickets.Count()})

Query c# List to limit children but return parents

I have a nested list structure with Customers -> Orders -> OrderItems. I am trying to find a LINQ or other query that will return the Customers and their nested items where the OrderItem quantity = 1. However, it should not return any Orders or OrderItems where the quantity != 1.
I have tried this:
var customers2 = customers.Where(c => c.Orders.Any(o => o.OrderItems.Exists(oi => oi.Quantity == 1)));
It correctly returns only Customers with order items quantity = 1, but it also returns all other Orders and Order Items as well.
I can get the desired results with a couple of For-each loops, but I would like to find something more elegant:
foreach (var customer in customers2)
{
customer.Orders = customer.Orders.Where(o => o.OrderItems.Exists(oi => oi.Quantity == 1)).ToList();
foreach (var order in customer.Orders)
{
order.OrderItems = order.OrderItems.Where(oi => oi.Quantity == 1).ToList();
}
}
Here is the object structure and some sample data.
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
public DateTime OrderDate { get; set; }
public bool Shipped { get; set; }
public List<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int OrderItemId { get; set; }
public int OrderId { get; set; }
public string ItemName { get; set; }
public int Quantity { get; set; }
}
var customers = new List<Customer>
{
new Customer
{
CustomerId = 1,
Name = "Shawn",
Address = "123 Main Street",
Orders = new List<Order>()
{
new Order()
{
OrderId = 100,
CustomerId = 1,
OrderDate = DateTime.Now,
Shipped = true,
OrderItems = new List<OrderItem>()
{
new OrderItem()
{
OrderItemId = 200,
OrderId = 100,
ItemName = "Computer",
Quantity = 1
},
new OrderItem()
{
OrderItemId = 206,
OrderId = 100,
ItemName = "Hard Drive",
Quantity = 2
}
}
},
new Order()
{
OrderId = 106,
CustomerId = 1,
OrderDate = DateTime.Now,
Shipped = true,
OrderItems = new List<OrderItem>()
{
new OrderItem()
{
OrderItemId = 207,
OrderId = 106,
ItemName = "Monitor",
Quantity = 3
},
new OrderItem()
{
OrderItemId = 208,
OrderId = 106,
ItemName = "DVD Burner",
Quantity = 2
}
}
}
}
},
new Customer
{
CustomerId = 2,
Name = "Arianna",
Address = "456 Main Street",
Orders = new List<Order>()
{
new Order()
{
OrderId = 101,
CustomerId = 2,
OrderDate = DateTime.Now.AddDays(-10),
Shipped = true,
OrderItems = new List<OrderItem>()
{
new OrderItem()
{
OrderItemId = 201,
OrderId = 101,
ItemName = "barbie",
Quantity = 2
}
}
}
}
},
new Customer
{
CustomerId = 3,
Name = "Ryan",
Address = "789 Main Street",
Orders = new List<Order>()
{
new Order()
{
OrderId = 102,
CustomerId = 3,
OrderDate = DateTime.Now.AddDays(-5),
Shipped = true,
OrderItems = new List<OrderItem>()
{
new OrderItem()
{
OrderItemId = 203,
OrderId = 103,
ItemName = "Minecraft",
Quantity = 2
}
}
}
}
}
};
You're on the right path with
var customers2 = customers
.Where(c => c.Orders.Any(o => o.OrderItems.Exists(oi => oi.Quantity == 1)));
You just need an additional step as you can't filter the orders and the customers at the same time, you already filtered the customers to get only those you're interested in, now filter the orders themselves
var customers2 = customers
.Where(c => c.Orders.Any(o => o.OrderItems.Exists(oi => oi.Quantity == 1)))
.Select(c => c.Orders.Where(o => o.OrderItems(o => o.OrderItems.Exists(oi => oi.Quantity == 1)));
This however leaves you with an ienumerable of ienumerable of orders, and not the customers, but you can't do exactly what you want (retrieve the customers and have their order property changed) as that would change their original order list, what you can do is create an Anonymous type to store both the orders and Customer in your query in the select as such:
var customers2 = customers
.Where(c => c.Orders.Any(o => o.OrderItems.Exists(oi => oi.Quantity == 1)))
.Select(c => new
{
Customer = c,
FilteredOrders = c.Orders.Where(o => o.OrderItems(o => o.OrderItems.Exists(oi => oi.Quantity == 1))
});
Now you can use this as such
foreach(var cust in customers2)
{
cust.Customer // your original Customer object
cust.Customer.Orders // your original orders collection for this Customer
cust.FilteredOrders // only the orders you're interested in for this customer
}
I imagine there's a shorter solution but this works:
var goodCusts = new List<Customer>();
foreach(var customer in customers)
{
var testCust = customer;
for (int i = testCust.Orders.Count - 1; i >= 0; i--)
{
if (testCust.Orders[i].OrderItems.Count != 1)
testCust.Orders.RemoveAt(i);
}
if (testCust.Orders.Any())
goodCusts.Add(testCust);
}
It does create a new collection, though. It just runs through each customer, removes any Orders with OrderItems.Count != 1, then tests if that customer has any Orders left. If it does, it gets added to the List<Customer> results.
Thanks to #StripplingWarrior I think I have arrived at the answer, though prob still not the most elegant:
var customers2 = customers.Where(x => x.Orders != null && x.Orders.Any(y => y.OrderItems != null && y.OrderItems.Any(z => z.Quantity == 1)));
customers2.ToList().ForEach(x =>
{
x.Orders.ForEach(y =>
{
y.OrderItems.RemoveAll(z => z == null || z.Quantity != 1);
});
x.Orders.RemoveAll(y => y == null || y.OrderItems.Count == 0);
});
return customers2;
This will get all customers with the specific orders where the order items quantity is the desired amount.
To use this it will remove all orderitems that do not have one item quantity. So you need to clone the list before using this function.
public static List<Customer> GetCustomersWithOrderItemQuantity(List<Customer> customers, int quantity)
{
var customers2 = customers.TakeWhile(c => c.Orders.Any(o => o.OrderItems.Any(oi => oi.Quantity == quantity))).ToList();
customers2.ForEach(cust => cust.Orders.ForEach(o => o.OrderItems.RemoveAll(oi => oi.Quantity != quantity)));
return customers2;
}
You can use like this to input a specific quantity.
var customers2 = GetCustomersWithOrderItemQuantity(customers, 1);
If you want all orders where at least one item has quantity of 1 then use this.
public static IEnumerable<Customer> GetCustomersWithOrderItemQuantity(List<Customer> customers, int quantity)
{
return customers.TakeWhile(c => c.Orders.Any(o => o.OrderItems.Any(oi => oi.Quantity == quantity)));
}
The above can be used the same as the other one but will show all orders with at least one order item quantity of 1 in your example above.

Categories

Resources