How to improve this LINQ query? - c#

I would appreciate any suggestions on improving this linq query. I am querying a list of invoices, with the objective of finding the customer with the highest invoice total for each month. I then want to display the year, month, customer and invoice total.
The invoice:
public class Invoice
{
public string Customer { get; set; }
public DateTime Date { get; set; }
public double Amount { get; set; }
}
The data context to create a list of invoices:
public class DataContext
{
private List<Invoice> _invoices;
private List<string> _customers;
public List<Invoice> Invoices
{
get
{
if (_invoices == null)
{
_customers = new List<string>(){ "Jim", "John", "Jeff", "Joe", "Jack"};
_invoices = new List<Invoice>();
Random random = new Random();
for (int i = 0; i < 1000; i++)
{
_invoices.Add(new Invoice() {
Customer = _customers[random.Next(0, 5)],
Date = new DateTime(random.Next(2010, 2015), random.Next(1, 13), random.Next(1, 20)),
Amount = random.Next(1,1000)
});
}
}
return _invoices;
}
}
}
The query:
DataContext context = new DataContext();
var invoiceTotalsByMonth = from invoice in context.Invoices
group invoice by invoice.Date.Year into yearGroup
orderby yearGroup.Key
select new
{
Year = yearGroup.Key,
Months = from year in yearGroup
group year by year.Date.Month into monthGroup
orderby monthGroup.Key
select new
{
Month = monthGroup.Key,
CustomerTotals = from month in monthGroup
group month by month.Customer into customerGroup
select new
{
Customer = customerGroup.Key,
Total = customerGroup.Sum(i=>i.Amount)
}
}
};
foreach (var year in invoiceTotalsByMonth)
{
Response.Write(year.Year + "<br/>");
foreach (var month in year.Months)
{
var maxCustomer = month.CustomerTotals.First(i => i.Total == month.CustomerTotals.Max(j => j.Total));
Response.Write(month.Month + ": " + maxCustomer.Customer + " - " + maxCustomer.Total.ToString("c") + "<br/>");
}
}
Thank you for your suggestions.

How about that:
DataContext context = new DataContext();
var invoiceTotalsByMonthQuery = from i in context.Invoices
group i by new { i.Date.Year, i.Date.Month } into g
select new
{
Year = g.Key.Year,
Month = g.Key.Month,
Customer = g.GroupBy(x => x.Customer)
.Select(x => new { Name = x.Key, Total = x.Sum(y => y.Amount)})
.OrderByDescending(x => x.Total)
.First()
};
var invoiceTotalsByMonth = invoiceTotalsByMonthQuery.OrderBy(x => x.Year)
.ThenBy(x => x.Month);
foreach(var item in invoiceTotalsByMonth)
{
Console.WriteLine("{0}/{1} - {2} ({3})", item.Month, item.Year, item.Customer.Name, item.Customer.Total);
}
One advice: it's probably better to use OrderBy + First instead of First + Max to find item with the max property value.

Related

Can you query total monthly amount paid by members from database table?

Payment Table Contains
PaymentID
MemberID
AmountPaid
Date
I want to query sum of amount for each month individually using linq in C#. Can you help me with querying this?
var result = (Your db context).tableName
.GroupBy(g => new { g.MonthColumn})
.Select(x => new
{
x.Key.MonthColumn,
amount = x.Sum(c => c.amount )
}).OrderBy(o => o.MonthColumn)
.ToList();
try this way it will help you to find your result
Good Question!
If your database has multiple year and multiple record in every month per member
var all = _context.PaymentTable.ToList();
var min = all.Min(f => f.Date).Year;
var max = all.Max(f => f.Date).Year;
List<PaidQueryViewModel> paidlist = new List<PaidQueryViewModel>();
for (int i = min; i < max; i++)
{
for (int m = 1; m < 11; m++)
{
PaidQueryViewModel paid = new PaidQueryViewModel();
var start = new DateTime(i, m, 1);
var end = start.AddMonths(1).AddMinutes(-1);
string Month = start.ToString("MMM-yyyy"); // if you want string month name
var amount = all.Where(f => f.Date >= start && f.Date <= end).Sum(f => f.AmountPaid);
paid.Month = start;
paid.Amount = amount;
paidlist.Add(paid);
}
}
public class PaidQueryViewModel
{
public DateTime Month { get; set; }
public decimal Amount { get; set; }
}

Max and min record from database using Entity Framework Core

I have a simple method that return rage of data from db according to some parameters, and it is simple:
public async Task<IEnumerable<P2PStats>> GetFilteredNetStats(ushort id, ushort remoteId, DateTime start, DateTime end)
{
using (var ctx = new DataContext())
{
IQueryable<P2PStats> query = ctx.P2PStats
.Where(stat => stat.Id == id && stat.Date >= start && stat.Date <= end)
.Where(stat => stat.P2PStatsDetailed.Any(detail => detail.RemoteId == remoteId))
.Select(stat => new P2PStats
{
Id = stat.Id,
AxmCardId = stat.Id,
Date = stat.Date,
P2PStatsDetailed = stat.P2PStatsDetailed.Where(detail => detail.RemoteId == remoteId).ToList()
});
return await query.ToListAsync();
}
}
It returns collection of P2PStats(well actually a task but in final result a collection). Can this be modified so that I can get only 2 values from database first with lowest date second with highest?
I tried Max and Min but only after the query has been materialized and I end up with max and min value or property not whole record.
This problem can be solved by the removal of filtering by Id and the addition of the method.Max() and .Min() to your code.
Below is an example of how this would look:
public async Task<IEnumerable<P2PStats>> GetNetStatsLowestAndHighestDate(ushort id, ushort remoteId, DateTime start, DateTime end)
{
using (var ctx = new DataContext())
{
IQueryable<P2PStats> query = ctx.P2PStats
.Where(stat => stat.Date >= start && stat.Date <= end)
.Where(stat => stat.P2PStatsDetailed.Any(detail => detail.RemoteId == remoteId))
.DefaultIfEmpty(0)
.Max(s => s.Date)
.Select(stat => new P2PStats
{
Id = stat.Id,
AxmCardId = stat.Id,
Date = stat.Date,
P2PStatsDetailed = stat.P2PStatsDetailed.Where(detail => detail.RemoteId == remoteId).ToList()
});
IQueryable<P2PStats> query2 = ctx.P2PStats
.Where(stat => stat.Date >= start && stat.Date <= end)
.Where(stat => stat.P2PStatsDetailed.Any(detail =>
detail.RemoteId ==
remoteId))
.DefaultIfEmpty(0)
.Min(s => s.Date)
.Select(stat => new P2PStats
{
Id = stat.Id,
AxmCardId = stat.Id,
Date = stat.Date,
P2PStatsDetailed =
stat.P2PStatsDetailed.Where(detail => detail.RemoteId == remoteId).ToList()
});
var results = query1.Concat(query2);
return await results.ToListAsync();
}
}
i assume you have records for the same ID. just created a linqpad example (i was not sure if you want list of min value, max value rows or just min and max)
the approach would be to group on same field (propably on ID) and pick min, max
void Main()
{
test t = new test();
var l = new List<test>() {
new test() {ID = 0, a1="aaa", a2 = 10},
new test() {ID = 1, a1="aaa", a2 = 40},
new test() {ID = 2, a1="aaa", a2 = 70},
new test() {ID = 3, a1="aaa", a2 = 50},
};
l.Dump("original");
l.GroupBy(g => g.a1).Select(s => new { max = s.Max(mm => mm.a2), min = s.Min(mi => mi.a2) }).Dump("return 2 values");
List<test> lRes = new List<test>();
lRes.Add(l.OrderBy(o => o.a2).First());
lRes.Add(l.OrderBy(o => o.a2).Last());
lRes.Dump("return table of min record and max record");
}
public class test
{
public int ID { get; set; }
public string a1 { get; set; }
public int a2 { get; set; }
public test() { }
}

foreach loop data index as the key

I have been trying to make for each loop with the index as the key
this case i want to made a logic if the input user is match with index and i will show foreach all of the data which has index as the key
I made two class like this
class DataContainer
{
public DataContainer()
{
}
public int index { get; set; }
public List<DataValue> DataValue { get; set; }
}
class DataValue
{
public DataValue()
{
IntegerValues = new List<int>();
}
public string name { get; set; }
public List<int> IntegerValues { get; set; }
}
after that i try to make list of datacontainer like this
List<DataContainer> harakatSininilMabsutoh = new List<DataContainer>(){
new DataContainer{index = 2015 , DataValue = new List<DataValue>()
{
new DataValue{name = "first",IntegerValues = {9,55,18,11}},
new DataValue{name = "second" ,IntegerValues = {5,54,18,11}},
new DataValue{name = "third" ,IntegerValues = {40,26,14,11}},
new DataValue{name = "four" ,IntegerValues = {22,0,5,10}},
new DataValue{name = "fifth" ,IntegerValues = {46,44,17,0}},
}
},
new DataContainer{index = 2013 , DataValue = new List<DataValue>()
{
new DataValue{name = "first",IntegerValues = {26,49,8,11}},
new DataValue{name = "second" ,IntegerValues = {19,42,8,11}},
new DataValue{name = "third" ,IntegerValues = {55,3,12,11}},
new DataValue{name = "fourth" ,IntegerValues = {27,4,23,8}},
new DataValue{name = "fifth" ,IntegerValues = {43,22,7,1}},
}
},
new DataContainer{index = 2001, DataValue = new List<DataValue>()
{
new DataValue{name = "first",IntegerValues = {35,44,27,10}},
new DataValue{name = "second" ,IntegerValues = {24,41,27,10}},
new DataValue{name = "third" ,IntegerValues = {36,30,26,10}},
new DataValue{name = "fourth" ,IntegerValues = {59,24,8,6}},
new DataValue{name = "fifth" ,IntegerValues = {29,27,26,1}},
}
}
};
and then i made a logic like this
int years = (this is user input);
if(years == 2015)
{
///How to for each this which has index 2015
}
else if (years = 2013)
{
//how to foreach this which has index 2013
}
else if (years = 2001)
{
//how to foreach this which has index 2001
The simplest is by using LINQ FirstOrDefault like this
int userinput = 2015;
DataContainer requested = harakatSininilMabsutoh.FirstOrDefault(x => x.index == userinput);
if (requested == null) //FirstOrDefault of a class will return null if not found
return;
foreach (DataValue val in requested.DataValue)
Console.WriteLine(val.name + ": " + string.Join(", ", val.IntegerValues));
Edit 2:
If you only need all the integers, without name, without anything else, then you could either do this to get the List<List<int>>:
int userinput = 2015;
List<List<int>> intValuesOnly = harakatSininilMabsutoh
.FirstOrDefault(x => x.index == userinput)
.DataValue
.Select(y => y.IntegerValues)
.ToList();
//Do whatever you want with intValuesOnly. This is everything that you have in a list of lists
or do this to get List<int> (flattened):
int userinput = 2015;
List<int> intValuesOnly = harakatSininilMabsutoh
.FirstOrDefault(x => x.index == userinput)
.DataValue
.SelectMany(y => y.IntegerValues)
.ToList();
//Do whatever you want with intValuesOnly. This is everything that you have in single list
As FirstOrDefault may return null if the userinput is not found, note that, if you are not using C#6, you may want to consider two steps LINQ:
int userinput = 2015;
DataContainer requested = harakatSininilMabsutoh.FirstOrDefault(x => x.index == userinput);
if (requested == null) //FirstOrDefault of a class will return null if not found
return;
List<List<int>> intValuesOnly = requested
.Select(y => y.IntegerValues)
.ToList();
//Do whatever you want with intValuesOnly. This is everything that you have in a list of lists
Firstly, note that in this line you have tried to use a type name as a property name:
public List<DataValue> DataValue { get; set; }
I've renamed this property to 'DataValues' as shown:
public List<DataValue> DataValues { get; set; }
You have a list ('harakatSininilMabsutoh'), each element of which is a DataContainer. Each DataContainer in the list has two properties: an index and a list of 'DataValues' (NB renamed from 'DataValue' in your post).
The looping logic you want will therefore be something like this:
var matchingYears = harakatSininilMabsutoh.Where(h => h.index == years);
foreach (var dataContainer in matchingYears)
{
foreach (var item in dataContainer.DataValues)
{
// Action required on each DataValue:
Debug.Print(item.name + " " + string.Join(",", item.IntegerValues));
}
}
You'll need to add the following 'using' statement to your class, since 'Where' is a LINQ extension method:
using System.Linq;
If you know that there will be exactly one matching year, you could add First() and remove the outer foreach loop. If you know there will be at most one matching year (but there could be zero), you can still remove the outer foreach loop but you should use FirstOrDefault() instead and test for null.

query to get all action during for each hour

i want to run and print a query that shows the number of orders per each hour in a day(24).
should look like:
hour-1:00, number of orders-5
hour-2:00, number of orders-45
hour-3:00, number of orders-25
hour-4:00, number of orders-3
hour-5:00, number of orders-43
and so on...
i try:
public void ShowBestHours()
{
using (NorthwindDataContext db = new NorthwindDataContext())
{
var query =
from z in db.Orders
select new Stime
{
HourTime = db.Orders.GroupBy(x => x.OrderDate.Value.Hour).Count(),
};
foreach (var item in query)
{
Console.WriteLine("Hour : {0},Order(s) Number : {1}", item.HourTime, item.Count);
}
}
}
public class Stime
{
public int HourTime { get; set; }
public int Count { get; set; }
}
You need to change your query to
var query =
from z in db.Orders
group z by z.OrderDate.Value.Hour into g
select new Stime{ HourTime = g.Key, Count=g.Count () };
or alternatively
var query = db,Orders.GroupBy (o => o.OrderDate.Value.Hour).Select (
g => new Stime{ HourTime=g.Key, Count=g.Count () });
In my copy of Northwind all of the OrderDate values are dates only so the result is just
HourTime = 0, Count = 830.
I'm assuming you're just experimenting with grouping. Try grouping by day of week like this
var query = db.Orders.GroupBy (o => o.OrderDate.Value.DayOfWeek).Select (
g => new { DayOfWeek=g.Key, Count=g.Count () });
which gives a more useful result.
You aren't setting Stime.Count anywhere in your query and you aren't grouping by hour correctly. I haven't seen your exact setup of course, but I think the following should work for you.
var query =
from z in db.Orders
group z by z.OrderDate.Value.Hour into g
select new Stime() { HourTime = g.Key, Count = g.Count() };
foreach (var item in query)
{
Console.WriteLine("Hour : {0},Order(s) Number : {1}", item.HourTime, item.Count);
}
Try this:
public void ShowBestHours()
{
using (NorthwindDataContext db = new NorthwindDataContext())
{
var query = db.Orders.GroupBy(x => x.OrderDate.Value.Hour).OrderByDescending(x => x.Count()).Select(x => new Stime { HourTime = x.Key, Count = x.Count() });
foreach (var item in query)
{
Console.WriteLine("Hour : {0},Order(s) Number : {1}", item.HourTime, item.Count);
}
}
}

Multiple group by and Sum LINQ

I have a products sales table that looks like this:
saleDate prod qty
10/22/09 soap 10
09/22/09 pills 05
09/25/09 soap 06
09/25/09 pills 15
I need to make the SUM of each MONTH so the final table would look like this:
saleDate prod qty
10/09 soap 10
09/09 soap 06
09/09 pills 20
Can I do this with LINQ?
var products = new[] {
new {SaleDate = new DateTime(2009,10,22), Product = "Soap", Quantity = 10},
new {SaleDate = new DateTime(2009,09,22), Product = "Pills", Quantity = 5},
new {SaleDate = new DateTime(2009,09,25), Product = "Soap", Quantity = 6},
new {SaleDate = new DateTime(2009,09,25), Product = "Pills", Quantity = 15}
};
var summary = from p in products
let k = new
{
//try this if you need a date field
// p.SaleDate.Date.AddDays(-1 *p.SaleDate.Day - 1)
Month = p.SaleDate.ToString("MM/yy"),
Product = p.Product
}
group p by k into t
select new
{
Month = t.Key.Month,
Product = t.Key.Product,
Qty = t.Sum(p => p.Quantity)
};
foreach (var item in summary)
Console.WriteLine(item);
//{ Month = 10/09, Product = Soap, Qty = 10 }
//{ Month = 09/09, Product = Pills, Qty = 20 }
//{ Month = 09/09, Product = Soap, Qty = 6 }
var q = from s in sales
group s by new {saleDate = s.saleDate.ToString("MM/yy"), s.prod} into g
select new { g.Key.saleDate, g.Key.prod, qty = g.Sum(s => s.qty) };
class Program
{
static void Main(string[] args)
{
var sales = new List<Sale>();
sales.Add(new Sale() { Product = "soap", saleDate = new DateTime(2009, 10, 22), Quantity = 10});
sales.Add(new Sale() { Product = "soap", saleDate = new DateTime(2009, 9,22), Quantity = 6});
sales.Add(new Sale() { Product = "pills", saleDate = new DateTime(2009,9,25), Quantity = 15});
sales.Add(new Sale() { Product = "pills", saleDate = new DateTime(2009,9,25), Quantity = 5});
var q = from s in sales
group s by new { s.Product, s.saleDate.Month, s.saleDate.Year } into g
select new {Month = String.Format("{0:MM/yy}", new DateTime(g.Key.Year, g.Key.Month, 1)), product = g.Key.Product, quantity = g.Sum(o=>o.Quantity)};
}
}
class Sale
{
public DateTime saleDate { get; set; }
public int Quantity { get; set; }
public string Product { get; set; }
}
Sure.
It'll look like this:
var GroupedSales =
from p in products
group p by p.saleDate.Month into g
select new { saleMonth = g.saleDate.Month, QtySold = g.Sum(p => p.qty) };
See: http://msdn.microsoft.com/en-us/vcsharp/aa336747.aspx#sumGrouped
Also, note that I was operating under the assumption that your dataset only had 2009 data. If you want to group by month/year, you can use saleDate.ToString("yyyyMM") instead of saleDate.Month.

Categories