Group by with a foreign key on single LINQ - c#

I have 2 related tables and they are
Schedule
SlotId
Description
Amount
and Slot
Id
Name
Example data:
Schedule
slotid description amount
--------------------------------
1 Morning charge 300
1 Late fee 300
1 Earlier bonus 200
1 Half day 150
2 Morning charge 300
2 Late fee 300
2 Earlier bonus 200
2 Half day 150
3 Morning charge 300
3 Late fee 300
3 Earlier bonus 200
3 Half day 150
Final result wanted as a SlotSchedules list, like this:
SlotId
SlotName
List<Schedules>
I need:
fetched list of Schedules
and then find distinct slots in it
and then iterate through each slot and build the model i needed as below
This is what I tried with LINQ:
List<Schedules> schedulesAll = (from n in dbContext.Schedules
select n).ToList();
var slotsdistinct = schedulesAll.Select(x => x.SlotId).Distinct().ToList();
foreach (var slot in slotsdistinct)
{
var scheduledforslot = schedulesAll.Where(x => x.SlotId == slot).Select(x => x).ToList();
foreach (Schedules _schedule in scheduledforslot)
{
//ListModel.Add(new DetailsModel { Name = _schedule.Description, Amount = (_schedule.Amount });
}
}
Any way to make it in single LINQ query?

Try this:
var slotsSchedules = dbContext.Slots.ToList().Select(slot => new {
SlotID = slot.Id,
Name = slot.Name,
SlotSchedules = dbContext.Schedules.ToList()
.Where(schedule = > schedule.SlotId == slot.Id).ToList()});

Related

unable to draw chart of highest sold products

I have a soldproduct table I want to draw the chart of the top 10 highest sold products.
trying it with following code but I don't know what is the exact problem while, it is logically right.
here is the error
System.ArgumentException: 'DbExpressionBinding requires an input expression with a collection ResultType. Parameter name: input'
DateTime start = startDate.Value;
DateTime end = EndDate.Value;
var TopTen = (from q in db.soldProduct
where (q.addDate >= start && q.addDate <= end)
orderby q.Barcode.Count() descending
select new
{
count = q.Barcode.Count(),
q.productName
}).Take(10);
foreach (var item in TopTen)
{
chartCustomer.Series["Customer"].Points.AddXY(item.productName, item.count);
}
ADDED :- I have in each row the item name and the quantity which might be one or more, I want to add that as well to the query but it is getting harder
model of the bill of soldProduct contains the following
public string barcode {get;set;}
public int productId{get;set;}
public string productName{get;set;}
public int quantity{get;set;}
public DateTime addDate{get;set;}
public double amount{get;set}
so the table if it has the following data
productId
barcode
productName
quantity
addDate
amount
1
111
milk
3
3/2/2020
15
1
111
milk
1
3/2/2020
5
2
222
bread
5
3/2/2020
20
3
333
cheese
1
3/2/2020
100
1
111
milk
3
3/2/2020
15
2
222
bread
3
3/2/2020
15
quantity of the of item sold the highest is bread with 8 occurrences it should come first then milk with 7 occurrences and then cheese.
Addition after comments: Apparently every SoldProduct has a property Amount. You don't want to count the number of SoldProducts, but the total Amount of all SoldProducts.
So if SoldProduct1 for product "Milk" has Amount 10 and SoldProduct[22] for product "Milk" has Amount 14, you want as result that of product "Milk" 24 items have been sold (= 10 + 14).
End addition after comment
So you have a table, SoldProducts. Every row in this table represents one Sale of a Product.
table SoldProducts has a.o. at least the following columns
AddDate: when was the sale
ProductName: the Name of the sold product,
Amount: the number of sold products in this sale.
I want ... the top 10 highest sold products
"highest sold products" probably doesn't have to do anything to do with the height of the sold products, but with the total Amount of products sold.
To calculate this, you need to make groups of SoldProducts with the same name. You'll get groups of the soldProducts with name: "Bread" and SoldProducts with name "Milk", etc.
Then you want to sum the total Amount of all soldProducts per group.
So if group "Bread" has 3 SoldProducts with Amounts 10, 7, 5, then you want as result: ["Bread", 22]. 22 is the total number of Breads sold.
You order the result of the grouping items by descending TotalAmount and take the first 10 items.
To group elements based on something common is done by one of the overloads of Enumerable.GroupBy. In this case, I take the overload with parameter resultSelector to count the number of elements in each group.
DateTime beginDate = ...
DateTime endDate = ...
var topTenSoldProductNames = dbContext.SoldProducts
.Where(soldProduct => beginDate <= soldProduct.AddDate
&& endDate >= soldProduct.AddDate)
// make groups with same ProductName
.GroupBy(soldProduct => soldProduct.ProductName,
// parameter resultSelector: take each ProductName,
// and all SoldProducts with this ProductName to make one new
(productName, soldProductsWithThisProductName) => new
{
Name = productName,
TotalAmount = soldProductsWithThisProductName
.Select(soldProduct => soldProduct.Amount)
.Sum(),
})
// order by descending TotalAmount, and take the first 10
.OrderByDescending(groupResult => groupResult.TotalAmount)
.Take(10);
Your query is wrong, you are returning count of chars. And without grouping this query is useless.
var groupingQuery =
from q in db.soldProduct
where (q.addDate >= start && q.addDate <= end)
group q by q.productName into g
select new
{
productName = g.Key,
count = g.Count(),
};
var TopTen = groupingQuery.OrderByDescending(x => x.count).Take(10);

Buy X Pay for Y Algorithm

I have to write "Buy X Pay for Y" algorithm.
Request that comes to my endpoint is list of Articles
public class Article
{
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
payFor variable comes from db and is defined by package discount id
Here's my algorithm that I wrote so far
if (purchasedQuantity >= minPurchaseQuantity)
{
var c = 0;
foreach (var article in articlesForPackage.OrderByDescending(a => a.UnitPrice))
{
for (var i = 1; i <= article.Quantity; i++)
{
c++;
if (c > payFor)
{
c = 0;
result.Add(new Discount
{
Value = article.UnitPrice
});
}
}
}
}
Unfortunately, this algorithm does not work in some cases.
When the package discount is defined buy 3 and pay for 2 it works, but if buy 3 pay for one doesn't work.
Could somebody help me?
This is how the algorithm should work:
We have 3 articles
1 Art1 - 20$
2 Art2 - 30$
3 Art3 - 40$
If minPurchaseQuantity is 3 and payFor is 2 it means that cost of Art1 should be added to result list (because it is the cheapest one)
If minPurchaseQuantity is 3 and payFor is 1 it means that cost of Art2 and Art1 should be added to result list (Now only the Art2 is adding)
Well, the main issue is, that you reset c as soon as it gets larger than payFor. This works as long as minPurchaseQuantity-payFor=1, but in other cases it won't.
While it's not as easy as the solution I presented in my first answer, I think the actual algorithm can be implemented more concisely. The following code first batches the items in groups eligible for discount. For each of the batches it then skips as many as payFor items and calculates the discount from the rest
// first batch the items in batches eligible for discount (e.g. batches of three in take 3, pay for x)
var batchedItems = BatchItemsEligibleForDiscount(items, minPurchaseQuantity);
var discounts = batchedItems.Select(batch => batch.Skip(payFor))
.SelectMany(batch => batch) // flatten nested IEnumerable to an IEnumerable<Artible>
.Select(article => new Discount() { Value = article.UnitPrice });
The BatchItemsEligibleForDiscount gets the batches that are eligible for discount (i.e. have 3 items each if it's "take 3, pay for X". Articles with a Quantity>1 are "exploded", i.e. if the quantity is 3, 3 distinct objects are created.
IEnumerable<IEnumerable<Article>> BatchItemsEligibleForDiscount(items, minPurchaseQuantity)
{
return items.OrderByDescending(article => article.UnitPrice)
.Select(article => Enumerable.Range(1, article.Quantity).Select(n => new Article() { Quantity = 1, UnitPrice = article.UnitPrice })) // "explode" articles
.SelectMany(item => item) // flatten to an IEnumerable<Article>
.Select((article, index) => new { article, index })
.GroupBy(x => x.index / minPurchaseQuantity)
.Where(group => group.Count() == minPurchaseQuantity) // only take batches elegible for discount
.Select(group => group.Select(x => x.article));
}
See this fiddle for a demonstration.
OLD ANSWER
Calculating the discount is way easier. You can calculate the number of bundles elegible for discount (if its take 3, pay for 2 and 8 items, you have two whole bundles of 3 items each). By calculating the difference between the items to take and the items to pay and multiplying it with the number of bundles and the price per item, you can calculate the discount
var numberOfDiscountableBundles = article.Quantity / amountOfItemsElegibleForDiscount;
var discount = numberOfDiscountableBundles * (amountOfItemsElegibleForDiscount - payFor) * article.UnitPrice;
Example: Take 3, pay for 1 with 8 items:
numberOfDiscountableBundles = 8 / 3 = 2 (integer division!)
discount = 2 * (3 - 1) * p = 2 * 2 * p = 4 * p
It's two discounted bundles of three items each (six items). Four of those items are not payed for (only one per bundle), hence the total price is discounted by four times the price of a unit.
You could encapsule this in a method
Discount CalculateDiscountForArticle(Article article, int amountOfItemsElegibleForDiscount, int payFor)
{
var numberOfDiscountableBundles = article.Quantity / amountOfItemsElegibleForDiscount;
var discount = numberOfDiscountableBundles * (amountOfItemsElegibleForDiscount - payFor) * article.UnitPrice;
return new Discount
{
Value = discount
};
}
And your original function gets as easy as
var discounts = articlesForPackage.OrderByDescending(a => a.UnitPrice)
.Select(a => CalculateDiscountForArticle(a, amountOfItemsElegibleForDiscount, payFor));
EDIT TO OLD ANSWER
If the discount is granted only once per customer and article, the calculation is a bit different
double discount = 0;
if(article.Quantity >= amountOfItemsElegibleForDiscount)
{
var discount = (amountOfItemsElegibleForDiscount - payFor) * article.UnitPrice;
}

Calculate total of a column using linq in c#

I have a structure like
ID Description Principal Interest Total
123 Paid 100 150 250
Balance 50 50 100
Total ? ? ?
124 Paid 100 150 250
Balance 50 50 100
Total ? ? ?
Class :
public class APIBillingHistory
{
public List<APIBillingHistoryDetails> BillingHistoryDetails;
}
public class APIBillingHistoryDetails
{
public string BillId;
public string Description;
public Decimal Principal;
public Decimal Interest;
public Decimal Total;
}
I would like to sum value for each column for each id. So in above example, Total for ID 123 for Principal would be 150, Interest 200 likewise. I checked this solution over here How to create Total column and Total row using LINQ but not able to get it.
Code:
responseObj = new APIBillingHistory
{
BillingHistoryDetails = billingHistory.BillingHistoryDetails
.GroupBy(detail => new { detail.BillId, detail.Description})
.Select(group => new APIBillingHistoryDetails
{
BillId = group.Key.BillId,
Description = group.Key.Description,
Principal = group.Sum(t => t.Principal),
Interest = group.Sum(t => t.Interest),
Total = group.Sum(t => t.Principal) + group.Sum(t => t.Interest)
}).Concat(new APIBillingHistoryDetails
{
Description = "Total",
/* Not sure what to write over here */
})
};
Edit:
To clarify what I am trying to achieve:
My source record list does not contain "Total" column & "Total" row. I would like to add it manually.
I Was able to figure out how to Add "Total" column which would contain Sum of values.
Principal & Interest will contain values in decimal format.
ID column is an integer. I would like to Display Total row for each ID
Values for Principal & Interest are being calculated by aggregating based on ID, After that calculation, only Total row should get displayed.
Any Suggestions?
You can't use Concat and reference previous data. You need to use SelectMany:
BillingHistoryDetails = billingHistory.BillingHistoryDetails
.GroupBy(detail => detail.BillId)
.SelectMany(bg => bg.Concat(new[] { new APIBillingHistoryDetails {
BillId = bg.First().BillId,
Description = "Total",
Principal = bg.Sum(b => b.Principal),
Interest = bg.Sum(b => b.Interest),
Total = bg.Sum(b => b.Total)
} }));
Assuming the current data
BillId Description Principal Interest
123 Paid 100 150
123 Balance 50 50
124 Paid 100 150
124 Balance 50 50
Then just group by the identity, creating the total object by summing up the grouped properties setting the description as "Total".
var totalsObj = new APIBillingHistory {
BillingHistoryDetails = billingHistory.BillingHistoryDetails
.GroupBy(detail => detail.BillId)
.Select(g => new APIBillingHistoryDetails {
BillId = g.Key,
Description = "Total",
Principal = g.Sum(detail => detail.Principal),
Interest = g.Sum(detail => detail.Interest),
Total = g.Sum(detail => detail.Principal) + g.Sum(detail => detail.Interest)
}).ToList()
};
and totalsObj.BillingHistoryDetails would have desired out put
After that calculation, only Total row should get displayed.
BillId Description Principal Interest Total
123 Total 150 200 350
124 Total 150 200 350

Count Dates which a specific Customer does not have a Shop in it

Here is an example Data Table:
ID Amount Date
--------------------------
1 3.000 2016/1/1
2 4.000 2016/1/1
1 6.000 2017/1/1
1 3.000 2018/1/1
3 2.000 2019/1/1
I need to count Dates which a specific Customers does not have a Shop in it.
for example ID 2 does not have a shop in 2017/1/1 and 2018/1/1, so the count will be 2. and the count for customer ID 3 will be 3 because He does not have a shop in 2016/1/1 and 2017/1/1 and 2018/1/1
I think I should use grouping but do not know how to count which I want for a specific ID!
orders.GroupBy(x => x.Date)......???
Assuming you have list of objects:
// number of all different dates
var datesCount = list.Select(i => i.Date).Distinct().Count();
int customerId = 2;
//number of dates with customer shopping
var customerDatesCount = list.Where(i => i.ID == customerId).Select(i => i.Date).Distinct().Count();
var nonShoppingDatesCount = datesCount - customerDatesCount;
You need two steps here.
Get all distinct order dates
var allDates = orders.Select(o => o.Date).Distinct();
Find those dates which customer don't have. Except operation will do that
var missingDates = allDates.Except(orders.Where(o => o.ID == 2).Select(o => o.Date));
If you need just number of missing dates, then use missingDates.Count() or (better) use solution by #Roma

convert an sql query into linq to entities method based

i have got this query in sql server server that do what i want..
how can i convert it to linq
select t1.IDHardware,h.DescricaoHardware
from dbo.ProcessoHardware t1
INNER JOIN
(select t2.IDHardware, max(t2.IDProcessoHardware) as maxVisit
from dbo.ProcessoHardware t2
group by t2.IDHardware,t2.IDProcesso) v ON
v.maxVisit = t1.IDProcessoHardware JOIN dbo.Hardware h ON t1.IDHardware=h.IDHardware
where t1.Estado=1 AND IDProcesso=1
this is where i am now...but i am unable to figure it past this point..
var ProcHardware = (from procHardware in db.ProcessoHardwares
where procHardware.IDProcesso == IDProcesso
select new { procHardware.IDHardware, procHardware.IDProcessoHardware, procHardware.IDProcesso, procHardware.Estado } into x
group x by new { x.IDHardware, x.IDProcesso, x.IDProcessoHardware, x.Estado } into t
let Max = t.Max(g => g.IDProcessoHardware)
select new { IDHardware = t.Key.IDHardware, Estado = t.Key.Estado, t.Key.IDProcesso,IDProcessoHardware=t.Key.IDProcessoHardware,cMax=Max }).ToList().Where(t => t.Estado == 1 && t.IDProcesso == IDProcesso && t.IDProcessoHardware==Max).Select(c => new VMProcessoChooseHardware
{
IDHardware = c.IDHardware
});
i have got this table that relates the Table hardware with a table Process.. this table is called processHardware. this table is discribed by: IDProcessHardware IDProcess IDHardware State
the field state can have 3 states (1-Insert, 2-Remove,3-Substitute).. so i can i have this:
IDProcessHardware IDProcess IDHardware State
1 10 1 1
2 10 2 1
3 10 1 2
4 10 1 1
5 20 1 1
what i want to get is get the IDHardware that were inserted but not removed from process.
so by giving the IDProcess = 10 i want to get the hardware with the hardware ids 1 and 2..
IDProcessHardware IDProcess IDHardware State
1 10 1 1
2 10 2 1
3 10 1 2
4 20 1 1
in the table above by giving the IDProcess 10 , it should give me the Hardware ids 2.
Thanks in advance...
After a lot of trial and error, and a lot of search i found this link
http://jetmathew.wordpress.com/2014/01/21/select-latest-record-from-recordset-using-sql-and-linq/
the guy was trying something similar to what i want..
so i pick the linq query and transform it..
this is what i have now
var ProcHardware = (from a in db.ProcessoHardwares
group a by new { a.IDHardware, a.IDProcesso } into latest
join b in db.ProcessoHardwares on new { dt = latest.Max(itm => itm.IDProcessoHardware) } equals new { dt = b.IDProcessoHardware }
select new { ID = b.IDHardware, Estado=b.Estado,IDProcesso=b.IDProcesso }).ToList().Where(t => t.Estado == 1 && t.IDProcesso == IDProcesso ).Select(c => new VMProcessoChooseHardware
{
IDHardware = c.ID
});
it still need the rest the information discribing the hardware like the serial number , or the description.
i will post here the complete query..

Categories

Resources