Get the best selling product by using C# LINQ - c#

I have a list of Receipts
IEnumerable<Receipt> Receipts =
new List<Receipt>()
{
new Receipt
{
Id = 1, CustomerId = 1, IsCheckedOut = false, OperationDate = new DateTime(2021, 1, 2),
ReceiptDetails = new List<ReceiptDetail>()
{
new ReceiptDetail { Id = 1, ProductId = 1, UnitPrice = 10, Product = ProductEntities.ElementAt(0), DiscountUnitPrice = 9, Quantity = 2, ReceiptId = 1 },
new ReceiptDetail { Id = 2, ProductId = 2, UnitPrice = 20, Product = ProductEntities.ElementAt(1), DiscountUnitPrice = 19, Quantity = 8, ReceiptId = 1},
new ReceiptDetail { Id = 3, ProductId = 3, UnitPrice = 25, Product = ProductEntities.ElementAt(2), DiscountUnitPrice = 24, Quantity = 1, ReceiptId = 1 },
}
},
new Receipt
{
Id = 2, CustomerId = 2, IsCheckedOut = false, OperationDate = new DateTime(2021, 1, 15),
ReceiptDetails = new List<ReceiptDetail>()
{
new ReceiptDetail { Id = 4, ProductId = 1, UnitPrice = 10, Product = ProductEntities.ElementAt(0), DiscountUnitPrice = 9, Quantity = 10, ReceiptId = 2 },
new ReceiptDetail { Id = 5, ProductId = 3, UnitPrice = 25, Product = ProductEntities.ElementAt(2), DiscountUnitPrice = 24, Quantity = 1, ReceiptId = 2 }
}
},
new Receipt
{
Id = 3, CustomerId = 1, IsCheckedOut = false, OperationDate = new DateTime(2021, 2, 15),
ReceiptDetails = new List<ReceiptDetail>()
{
new ReceiptDetail { Id = 6, ProductId = 1, UnitPrice = 10, Product = ProductEntities.ElementAt(0), DiscountUnitPrice = 9, Quantity = 10, ReceiptId = 3 },
new ReceiptDetail { Id = 7, ProductId = 2, UnitPrice = 25, Product = ProductEntities.ElementAt(1), DiscountUnitPrice = 24, Quantity = 1, ReceiptId = 3 }
}
}
};
I need to get the best selling(by quantity) products based on CustomerId
Here is my function
public IEnumerable<Product> GetMostSoldProductByCustomer(int customerId, int productCount)
{
var a = ReceiptEntities.Where(rd => rd.CustomerId == customerId)..SelectMany(x => x.ReceiptDetails);
var b = a.GroupBy(rd => rd.ProductId);
var c = b.Select(p => p.Select(y => y.Quantity).Sum());
}
I'm stuck on this, I have no idea how to connect b and c.
For better understanding, if customerId = 1 and productCount = 3. The function should return 3 products with Id = 1, 2, 3 accordingly in descending order by quantities
One note! The customer whose id = 1 has two receipts, that's why I'm calculating the sum of Quantity as there are the same products in different receipts

Try the following query:
public IEnumerable<int> GetMostSoldProductByCustomer(int customerId, int productCount)
{
var query =
from re in ReceiptEntities
where re.CustomerId == customerId
from rd in re.ReceiptDetails
group rd by rd.ProductId into g
select new
{
ProductId = g.Key,
TotalQuantity = g.Sum(x => x.Quantity)
} into s
orderby descending s.TotalQuantity
select s.ProductId;
return query;
}

Related

Select duplicated items with non duplicates in the same collection

(Edited)
This is my code with examples of the collections I am working with:
That's what I meant when I wrote that myProducts1 and myProducts1 are nested collections
List<MyProducts> myProducts1= {
new MyProducts{id = 1, Name = "product1", isExcl= true},
new MyProducts{id = 2, Name = "product2", isExcl= false},
new MyProducts{id = 3, Name = "product3", isExcl= true},
new MyProducts{id = 4, Name = "product4", isExcl= false}
}
List<MyProducts> myProducts2= {
new MyProducts{id = 5, Name = "product5", isExcl= true},
new MyProducts{id = 6, Name = "product6", isExcl= false}
}
IEnumerable<SelectedProductRequest> selectedProducts = {
new SelectedProductRequest {id = 1, Name = "product1", Price = 23},
new SelectedProductRequest {id = 1, Name = "product1", Price = 44},
new SelectedProductRequest {id = 2, Name = "product2", Price = 11},
new SelectedProductRequest {id = 6, Name = "product6", Price = 34},
}
List<CategoryProduct> productsWithCategories = {
{CategoryName= "Category1", categoryProduct = myProducts1 },
{CategoryName= "Category2", categoryProduct = myProducts2 }
}
Here is my first code:
IEnumerable<SelectedProductViewModel> products1 =
.GroupBy(categoryProduct => categoryProduct .CategoryName)
.Select(categoryProduct => new ProductCategoryViewModel(
categoryProduct .Key,
categoryProduct
.Select(product => new SelectedProductViewModel(
product.Name,
selectedProducts.FirstOrDefault(selectedProduct => selectedProduct.id== product.id)?.Price ?? 0,
product.IsExclusive))
.OrderByDescending(product => product.id)))
Here is my second code:
IEnumerable<SelectedProductViewModel> products2 =
.GroupBy(categoryProduct => categoryProduct .CategoryName)
.Select(categoryProduct => new ProductCategoryViewModel(
categoryProduct .Key,
categoryProduct
.Join(contractSelectedProducts, product => product.id, selected => selected.id, (product, selected) =>
new SelectedProductViewModel(
product.Name,
selected?.Price ?? 0,
product.IsExclusive))
The results I get with those pieces of code:
products1= {"Category1",{
new SelectedProductRequest {id = 1, Name = "product1", Price = 23, IsExclusive = true},
new SelectedProductRequest {id = 2, Name = "product2", Price = 11, IsExclusive = false},
new SelectedProductRequest {id = 3, Name = "product3", Price = 0, IsExclusive = true},
new SelectedProductRequest {id = 4, Name = "product4", Price = 0, IsExclusive = false}
},
"Category2", {new SelectedProductRequest {id = 5, Name = "product5", Price = 0, IsExclusive = true},
new SelectedProductRequest {id = 6, Name = "product6", Price = 34, IsExclusive = false}}
products2= {"Category1",{
new SelectedProductRequest {id = 1, Name = "product1", Price = 23, IsExclusive = true},
new SelectedProductRequest {id = 1, Name = "product1", Price = 44, IsExclusive = true},
new SelectedProductRequest {id = 2, Name = "product2", Price = 11, IsExclusive = false}
}},
"Category2", {new SelectedProductRequest {id = 6, Name = "product6", Price = 34, IsExclusive = false}
But the result I want to achieve is this:
products= {"Category1",{
new SelectedProductRequest {id = 1, Name = "product1", Price = 23, IsExclusive = true},
new SelectedProductRequest {id = 1, Name = "product1", Price = 44, IsExclusive = true},
new SelectedProductRequest {id = 2, Name = "product2", Price = 11, IsExclusive = false},
new SelectedProductRequest {id = 3, Name = "product3", Price = 0, IsExclusive = true},
new SelectedProductRequest {id = 4, Name = "product4", Price = 0, IsExclusive = false}
}},
"Category2",{
{new SelectedProductRequest {id = 5, Name = "product5", Price = 0, IsExclusive = true},
{new SelectedProductRequest {id = 6, Name = "product6", Price = 34, IsExclusive = false}
}
(edited code)
My question: How can I achieve this result without using products1 and products2? Or how can i do it in a better way?
updated as per comment
Solution 1
I changed the last Linq query by :
List<ProductCategory> mergedList = products1
// concat products2 with products1
.Concat(products2)
// group by category name
.GroupBy(x => x.CategoryName)
//dictionay(categoryName, list of SelectedProductRequests)
.ToDictionary(key => key, value => value.SelectMany(x => x.SelectedProductRequests)
.GroupBy(x => new { x.id, x.Name, x.Price, x.IsExclusive })
// take the first in grouped element
.Select(x => x.First())
//convert to SelectedProductRequest
.Select(x => new SelectedProductRequest { id = x.id, Name = x.Name, Price = x.Price, IsExclusive = x.IsExclusive })
.OrderBy(x => x.id)//order by id
.ToList())
.Select(x => new ProductCategory { CategoryName = x.Key.Key, SelectedProductRequests = x.Value })
.ToList();
Solution 2
if i understand your demand, you need to build productsWithCategories to get Expected result with one linq request, then check the following proposition by using GroupJoin instead Join :
IEnumerable<ProductCategoryViewModel> result = productsWithCategories
.Select(x => new ProductCategoryViewModel
{
CategoryName = x.CategoryName,
SelectedProductViewModels = x.categoryProduct
.GroupJoin(selectedProducts, prd => prd.id, sel => sel.id,
(prd, sel) => sel != null && sel.Any() ?
sel.Select(y =>
new SelectedProductViewModel
{
id = prd.id,
isExcl = prd.isExcl,
Name = prd.Name,
Price = y.Price
}).ToList() :
new List<SelectedProductViewModel>
{
new SelectedProductViewModel
{
id = prd.id,
isExcl = prd.isExcl,
Name = prd.Name,
Price = 0
}
})
.SelectMany(z => z)
.ToList()
});
i hope that will help you fix the issue
You can use Concat method to join arrays:
var selectedProductsMapped = selectedProducts.Select(s => new SelectedProductRequest
{
id = s.id,
Name = s.Name,
Price = s.Price,
isExclusive = myProducts.Where(p => p.id == s.id).FirstOrDefault().isExclusive
});
var exlcludedProducts = myProducts.Where(p => !selectedProducts.Any(sp => sp.id == p.id))
.Select(s => new SelectedProductRequest
{
id = s.id,
Name = s.Name,
Price = 0
});
var result = selectedProductsMapped.Concat(exlcludedProducts);
An example:
var selectedProducts = new List<SelectedProductRequest> {
new SelectedProductRequest {id = 1, Name = "product1", Price = 23},
new SelectedProductRequest {id = 1, Name = "product1", Price = 44},
new SelectedProductRequest {id = 2, Name = "product2", Price = 11}
};
var myProducts = new List<MyProducts> {
new MyProducts{id = 1, Name = "product1", isExclusive= true},
new MyProducts{id = 2, Name = "product2", isExclusive= false},
new MyProducts{id = 3, Name = "product3", isExclusive= true},
new MyProducts{id = 4, Name = "product4", isExclusive= false}
};
var selectedProductsMapped = selectedProducts.Select(s => new SelectedProductRequest
{
id = s.id,
Name = s.Name,
Price = s.Price,
isExclusive = myProducts.Where(p => p.id == s.id).FirstOrDefault().isExclusive
});
var exlcludedProducts = myProducts.Where(p => !selectedProducts.Any(sp => sp.id == p.id))
.Select(s => new SelectedProductRequest
{
id = s.id,
Name = s.Name,
Price = 0
});
var result = selectedProductsMapped.Concat(exlcludedProducts);
You can produce the set of products1 and products2 with Linq's Union(). You will need to implement SelectedProductRequest's GetHashCode().
products = products1.Union(products2);
Try it Online!
Add this to SelectedProductRequest:
public class SelectedProductRequest : IEquatable<SelectedProductRequest>
{
public int id {get;set;}
public string Name {get;set;}
public int Price {get;set;}
public bool IsExclusive {get;set;}
public bool Equals(SelectedProductRequest other) => other is null
&& this.id == other.id
&& this.Name == other.Name
&& this.Price == other.Price
&& this.IsExclusive == other.IsExclusive;
public override bool Equals(object obj) => Equals(obj as SelectedProductRequest);
public override int GetHashCode() => (id, Name, Price, IsExclusive).GetHashCode();
}

Complex linq join in EF6

There are two entities, for example, job and solution.
Each of them has a date field and a level field and a quantity field.
It is necessary to combine them so that they are grouped first by level, then by month, and at the same time, their quantity must be summed up.
I tried different options, but nothing comes out at all. The main problem is grouping by months and summing the numbers in the enclosed sheets.
That is, the output should be one sequence of summed numbers, grouped by level, and then by month.
For example:
var jobs = new List<Job>()
{
new Job { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 111 },
new Job { Level = 1, Date = new DateTime(2019, 1, 20), Quantity = 222 },
new Job { Level = 2, Date = new DateTime(2019, 2, 1), Quantity = 333 },
new Job { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 444 }
};
var solutions = new List<Solution>()
{
new Solution { Level = 1, Date = new DateTime(2019, 2, 1), Quantity = 555 },
new Solution { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 666 },
new Solution { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 777 },
new Solution { Level = 2, Date = new DateTime(2019, 1, 20), Quantity = 888 }
};
Output:
Level 1 -> 1 Jan 2019 -> 1110 (111 + 222 + 777)
Level 1 -> 1 Feb 2019 -> 555
Level 2 -> 1 Jan 2019 -> 888
Level 2 -> 1 Feb 2019 -> 1443 (333 + 444 + 666)
And so on. And yes, all this is in EF6.
Try following which uses Concat. I create a class for the merging. It can also be done anonymously.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication116
{
class Program
{
static void Main(string[] args)
{
var jobs = new List<Job>()
{
new Job { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 111 },
new Job { Level = 1, Date = new DateTime(2019, 1, 20), Quantity = 222 },
new Job { Level = 2, Date = new DateTime(2019, 2, 1), Quantity = 333 },
new Job { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 444 }
};
var solutions = new List<Solution>()
{
new Solution { Level = 1, Date = new DateTime(2019, 2, 1), Quantity = 555 },
new Solution { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 666 },
new Solution { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 777 },
new Solution { Level = 2, Date = new DateTime(2019, 1, 20), Quantity = 888 }
};
List<LevelDateQuantity> concat = jobs.Select(x => new LevelDateQuantity() { Date = x.Date, Level = x.Level, Quantity = x.Quantity})
.Concat( solutions.Select(x => new LevelDateQuantity() { Date = x.Date, Level = x.Level, Quantity = x.Quantity})).ToList();
List<LevelDateQuantity> results = concat.OrderBy(x => x.Level).ThenBy(x => x.Date)
.GroupBy(x => new { level = x.Level, date = new DateTime(x.Date.Year, x.Date.Month,1)})
.Select(x => new LevelDateQuantity() { Level = x.Key.level, Date = x.Key.date, Quantity = x.Sum(y => y.Quantity)})
.ToList();
}
}
public class LevelDateQuantity
{
public int Level { get; set; }
public DateTime Date { get; set; }
public int Quantity { get; set; }
}
public class Job : LevelDateQuantity
{
public int Level { get; set; }
public DateTime Date { get; set; }
public int Quantity { get; set; }
}
public class Solution : LevelDateQuantity
{
public int Level { get; set; }
public DateTime Date { get; set; }
public int Quantity { get; set; }
}
}
Oath, because we can not see your poco class structure we don't know if the two tables are seperate or has a one to many relation to a master table, so by the code you have provided I would do this ;
var jobs = new List<Job>()
{
new Job { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 111 },
new Job { Level = 1, Date = new DateTime(2019, 1, 20), Quantity = 222 },
new Job { Level = 2, Date = new DateTime(2019, 2, 1), Quantity = 333 },
new Job { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 444 }
};
var solutions = new List<Solution>()
{
new Solution { Level = 1, Date = new DateTime(2019, 2, 1), Quantity = 555 },
new Solution { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 666 },
new Solution { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 777 },
new Solution { Level = 2, Date = new DateTime(2019, 1, 20), Quantity = 888 }
};
foreach (var sol in solutions)
{
var jb = new Job();
jb.Level = sol.Level;
jb.Date = sol.Date ;
jb.Quantity= sol.Quantity;
jobs.Add(jb);
}
var result = Jobs.GroupBy(x=> new { x.Level, x.Date}).Select(x=> new
{
level = x.Key.Level,
date = x.Key.Date,
sumQ = x.Sum(y => y.Quantity )
});
I haven't tested the code and not wrote in in a compiler so there might be some typeerrors apart from that this should solve your problem.

How to sort collection of anonymous types

Using Linq to SQL I getting from database anonymous collection.The collection looks like:
1. { PS_DID = 523, ID = 2, TITLE = "Station1", ACTIVE = 1, status = null, validFrom = null }
2. { PS_DID = 402, ID = 4, TITLE = "Station2", ACTIVE = 1, status = null, validFrom = null }
3. { PS_DID = 8, ID = 152, TITLE = "Station3", ACTIVE = 1, status = 5, validFrom = {5/26/2015 12:00:01 AM} }
4. { PS_DID = 8, ID = 152, TITLE = "Station3", ACTIVE = 1, status = 5, validFrom = {5/26/2015 12:00:01 AM} }
5. { PS_DID = 13, ID = 75, TITLE = "Station4", ACTIVE = 1, status = 5, validFrom = {1/12/2016 12:00:01 AM} }
6. { PS_DID = 13, ID = 75, TITLE = "Station4", ACTIVE = 1, status = 5, validFrom = {12/1/2015 12:00:01 AM} }
7. { PS_DID = 13, ID = 75, TITLE = "Station4", ACTIVE = 1, status = 3, validFrom = {3/20/2016 12:00:01 AM} }
8. { PS_DID = 13, ID = 75, TITLE = "Station4", ACTIVE = 1, status = 1, validFrom = {5/19/2016 11:07:00 PM} }
9. { PS_DID = 13, ID = 75, TITLE = "Station4", ACTIVE = 1, status = 1, validFrom = {5/20/2016 3:50:00 PM} }
I want to order the anonymous collection by the ID of station, where each station would include a list of distinct statuses and each status would include a list of distinct dates.
A new collection should looks like this:
1. PS_DID = 523, ID = 2, TITLE = "Station1", ACTIVE = 1, status = null, validFrom = null
2. PS_DID = 402, ID = 4, TITLE = "Station2", ACTIVE = 1, status = null, validFrom = null
3. PS_DID = 8, ID = 152, TITLE = "Station3", ACTIVE = 1,
- status
* 5 - (validFrom = 5/26/2015 12:00:01 AM)
4. PS_DID = 13, ID = 75, TITLE = "Station4", ACTIVE = 1
- status
* 5 - validFrom
* 1/12/2016 12:00:01 AM
* 2/1/2015 12:00:01 AM
* 3 - validFrom
* 3/20/2016 12:00:01 AM
* 1 - validFrom
* 5/19/2016 11:07:00 PM
* 5/20/2016 3:50:00 PM
Can anyone help me?
What you need is grouping, you could use Linq for this.
You have to group your input collection twice, once on ID field and next on status field.
var results= collection.GroupBy(g=> new {g.PS_DID, g.ID , g.TITLE, g.ACTIVE})
.OrderBy(x=>x.Key.PS_DID)
.Select(x=> new
{
PS_DID = g.Key.PS_DID,
ID = g.Key.ID,
TITLE = g.Key.TITLE,
ACTIVE = g.Key.ACTIVE,
STATUS_VALID_DATE = x.GroupBy(g=> g.status)
.Select(s=> new {
status = s.Key,
validFrom = s.Select(v=>v.validrom).ToList()
}).ToList()
});

linq or sql smart way to make a separate order by

Data:
I want to order data like the image above:
var expiredDate = DateTime.Now.AddDays(-6);
var query=*;
var first=query.Search(o=>o.PreorderTime<expiredDate&&(o.TotalMoney-o.MoneyPaid)>0); // this on the top
var second=query.Search(o=>o.PreorderTime>=expiredDate&&(o.TotalMoney-o.MoneyPaid)>0);
var third=query.Search(o=>(o.TotalMoney-o.MoneyPaid)<=0);
var left= query.Search(o=>!first.Contains(o)&&!second.Contains(o)&&!third.Contains(o));
var all = first.Concat(second).Concat(third).Concat(left);
var result=all.AsEnumerable().Select((item, index) => new{...,Index=index}).Take(pagesize).OrderBy(o=>o.Index).Skip(pagesize*(pageindex-1));
I test the result ,I can get first page, but after second page,no data.I don't know why.
Is there a smart way to order this data?
Something like this should work (hope you can manage the actual projection)
var query = new[]
{
new { Id = 1, PreorderTime = new DateTime(2015, 11, 11), Name = "Jim", MoneyPaid = 0, TotalMoney = 5000 },
new { Id = 2, PreorderTime = new DateTime(2015, 11, 09), Name = "Sim", MoneyPaid = 500, TotalMoney = 5000 },
new { Id = 3, PreorderTime = new DateTime(2015, 11, 10), Name = "Sim", MoneyPaid = 1100, TotalMoney = 5000 },
new { Id = 4, PreorderTime = new DateTime(2015, 11, 19), Name = "Long", MoneyPaid = 3200, TotalMoney = 5000 },
new { Id = 5, PreorderTime = new DateTime(2015, 11, 29), Name = "Long", MoneyPaid = 200, TotalMoney = 5000 },
new { Id = 6, PreorderTime = new DateTime(2015, 11, 08), Name = "Long", MoneyPaid = 5000, TotalMoney = 5000 },
new { Id = 7, PreorderTime = new DateTime(2015, 11, 28), Name = "Long", MoneyPaid = 5000, TotalMoney = 5000 },
};
var expiredDate = DateTime.Now.AddDays(-6);
int pageSize = 3;
int pageCount = (query.Length + pageSize - 1) / pageSize;
for (int pageIndex = 1; pageIndex <= pageCount; pageIndex++)
{
var page = query
.OrderBy(o => o.TotalMoney - o.MoneyPaid > 0 ? o.PreorderTime < expiredDate ? 0 : 1 : 2)
.ThenByDescending(o => o.PreorderTime)
.Skip(pageSize * (pageIndex - 1))
.Take(pageSize)
//.Select(...)
.ToList();
}

Group by using linq (range + count)

var data = new[] {
new { Id = 0, Cat = 1, Price = 2 },
new { Id = 1, Cat = 1, Price = 10 },
new { Id = 2, Cat = 1, Price = 30 },
new { Id = 3, Cat = 2, Price = 50 },
new { Id = 4, Cat = 2, Price = 120 },
new { Id = 5, Cat = 2, Price = 200 },
new { Id = 6, Cat = 2, Price = 1024 },
};
var ranges = new[] { 10, 50, 100, 500 };
Needed output is grouped price count by equal or greater than the range used according categories.
(in one linq statement)
cat range count
-------------------------------------
1 10 2 (In 1. categories there is 2 item that price >= 10(range) [10;30])
2 10 4 (In 2. categories there is 4 item that price >= 10(range) [50;120;200;1024])
2 50 4 ....
2 100 3 ....
2 500 1 (In 2. categories there is 1 item that price >= 500(range) [1024])
Try this:
var data = new[] {
new { Id = 0, Cat = 1, Price = 2 },
new { Id = 1, Cat = 1, Price = 10 },
new { Id = 2, Cat = 1, Price = 30 },
new { Id = 3, Cat = 2, Price = 50 },
new { Id = 4, Cat = 2, Price = 120 },
new { Id = 5, Cat = 2, Price = 200 },
new { Id = 6, Cat = 2, Price = 1024 },
};
var ranges = new[] { 10, 50, 100, 500 };
var result = from r in ranges
from g in data
where g.Price >= r
select new {g.Cat, Price=r};
var groupedData =
from d in result
group d by new{d.Cat, d.Price} into g
select new{Cat=g.Key.Cat, Price=g.Key.Price, TotalCount=g.Count()};
This should work:
var values =
data.SelectMany(x => ranges.Where(y => x.Price >= y)
.Select(y => new { Record = x, Range = y }))
.GroupBy(x => new { Cat = x.Record.Cat, Range = x.Range })
.Select(x => new { Cat = x.Key.Cat, Range = x.Key.Range, Count = x.Count()});
Results:
{ Cat = 1, Range = 10, Count = 2 }
{ Cat = 2, Range = 10, Count = 4 }
{ Cat = 2, Range = 50, Count = 4 }
{ Cat = 2, Range = 100, Count = 3 }
{ Cat = 2, Range = 500, Count = 1 }

Categories

Resources