Complex linq join in EF6 - c#

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.

Related

LINQ reusing Select statement with anonymous grouping types

I am trying to find a way to cut out repeated code in an application that center around LINQ select statements. Lets say we have existing table rows that need to be aggregated and grouped for different reporting requirements and all original data is just grouped by day and needs to be grouped by week / month and another property.
DataRow is an example object that needs to be grouped and converted into an object ReportTableRow (please note this is a much reduced object but the actual objects have far more properties and therefore become much more drawn out).
public class DataRow
{
public DateTime Date { get; set; }
public string AccountNumber { get; set; }
public string MachineNumber { get; set; }
public int TEST { get; set; }
}
public class ReportTableRow
{
public int WeekNumber { get; set; }
public int Month { get; set; }
public string AccountNumber{ get; set; }
public string MachineNumber { get; set; }
public int TEST { get; set; }
public string TEST_TRAFFICLIGHT { get; set; }
}
And we create a list of DataRows:
List<DataRow> reportTable = new List<DataRow>()
{
new DataRow()
{
Date = new DateTime(2021, 06, 14),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 1
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 6
},
new DataRow()
{
Date = new DateTime(2021, 06, 16),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 4
},
new DataRow()
{
Date = new DateTime(2021, 06, 17),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 18),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 7
},
new DataRow()
{
Date = new DateTime(2021, 06, 19),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 20),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 11
},
new DataRow()
{
Date = new DateTime(2021, 06, 14),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 1
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 6
},
new DataRow()
{
Date = new DateTime(2021, 06, 16),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 4
},
new DataRow()
{
Date = new DateTime(2021, 06, 17),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 18),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 7
},
new DataRow()
{
Date = new DateTime(2021, 06, 19),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 20),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 11
}
};
So if we either need the data grouped "BY WEEK" or "BY MONTH" then we need to actually return in the report the WeekNumber or Month number respectively and the grouping would look something like this where GetTrafficLight method returns a string value based on the value of the sum of TEST:
switch (aggregate.ToUpper())
{
case "BY WEEK":
reportTable = reportTable
.GroupBy(x => new { x.AccountNumber, WeekNumber = CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(x.Date, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday) })
.Select(x => new ReportTableRow
{
WeekNumber = x.Key.WeekNumber,
Month = x.Max(y => y.Date).Month,
MachineNumber = x.FirstOrDefault().MachineNumber,
TEST = x.Sum(y => y.TEST),
TEST_TRAFFICLIGHT = GetTrafficLight(x.Sum(y => y.TEST)
})
.ToList();
break;
case "BY MONTH":
reportTable = reportTable
.GroupBy(x => new { x.AccountNumber, x.Date.Month })
.Select(x => new ReportTableRow
{
WeekNumber = CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(x.Max(y => y.Date), CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday),
Month = x.Key.Month,
MachineNumber = x.FirstOrDefault().MachineNumber,
TEST = x.Sum(y => y.TEST),
TEST_TRAFFICLIGHT = GetTrafficLight(x.Sum(y => y.TEST)
})
.ToList();
break;
}
The question is, is there anyway to remove the "Select" code and pass it into either a method or object that would accept an anonymous grouping so that it can be reused multiple times. Changing the anonymous grouping to a compile time object that contains two properties means duplicates are then returned in the dataset, could be that finding a way to remove the duplicates in compile time grouped members might help to resolve?
Please note, the code has been created to pose this question.
This is optional, but the first thing I would do is put that "aggregate" into a boolean variable:
bool byWeek = aggregate.ToUpper() == "BY WEEK";
The next thing I would do is capture and tame that "GetWeekOfYear" logic:
int getFirstMonday (DateTime date) =>
CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(
date,
CalendarWeekRule.FirstFourDayWeek,
DayOfWeek.Monday
);
Doing these things makes it more feasible to put the conditional logic inside the GroupBy and Select methods, making it so that you don't have to do the whole thing twice:
List<ReportTableRow> results =
reportTable
.GroupBy(x => new {
x.AccountNumber,
TimeBin = byWeek ? getFirstMonday(x.Date): x.Date.Month
})
.Select(x => new ReportTableRow {
WeekNumber = byWeek ? x.Key.TimeBin : getFirstMonday(x.Max(y => y.Date)),
Month = byWeek ? x.Max(y => y.Date).Month : x.Key.TimeBin,
MachineNumber = x.FirstOrDefault().MachineNumber,
TEST = x.Sum(y => y.TEST),
TEST_TRAFFICLIGHT = GetTrafficLight(x.Sum(y => y.TEST))
})
.ToList();
I should note that your sample data makes it so that the results are the same regardless of 'aggregate' setting. But if you change it a bit, such as changing the days for some of the entries, you'll get different results in the aggregation. And at least for the changes I made my code repeats the behavior of your code when toggling 'aggregate'.

need to construct a clean Linq query for Graph Data

i am trying to populate a graph of balances over the last 2 years. For example its 2020 i want to return a structure like
if it would have been 2021 today i need to returns data of 2019 and 2020.
my class structure looks like this
public class Transaction : BaseEntity
{
public TransactionType TransactionType { get; set; }
public DateTime TransactionDate { get; set; }
public double TransactionAmount { get; set; }
}
public enum TransactionType{
Deposit = 0,
Withdraw = 1
}
i populated this structure and thought it will be simple. i have
var transactions = new ICollection<Transaction>
here is an example seed data of January of 2018
modelBuilder.Entity<Transaction>(b =>
{
b.HasData(new
{
Id = Guid.NewGuid().ToString(),
AccountId = "37846734-172e-4149-8cec-6f43d1eb3f60",
TransactionAmount = 3334.38,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 20),
TransactionType = TransactionType.Deposit
});
b.HasData(new
{
Id = Guid.NewGuid().ToString(),
AccountId = "37846734-172e-4149-8cec-6f43d1eb3f60",
TransactionAmount = -3334.38,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 21),
TransactionType = TransactionType.Withdraw
});
b.HasData(new
{
Id = Guid.NewGuid().ToString(),
AccountId = "37846734-172e-4149-8cec-6f43d1eb3f60",
TransactionAmount = 1000.23,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 25),
TransactionType = TransactionType.Deposit
});
As you can see in January of 2018 3334.38 was added and The same amount was subtracted and 1000.23 was added so i should get 2018 under that January 1000.23
var transactions = await _unitOfWork.TransactionRepositoy.GetAllAsync();
transactions.GroupBy(x => x.TransactionDate.Year);
var data = transactions.Select(k => new { k.TransactionDate.Year, k.TransactionDate.Month, k.TransactionAmount }).GroupBy(x => new { x.Year, x.Month }, (key, group) => new
{
yr = key.Year,
mnth = key.Month,
tBalance = group.Sum(k => k.TransactionAmount)
}).ToList();
but in january of 2018 i am getting
I am trying to group by year and in those group i am trying to get Month and Total Balance.
i have a group on month and group.Sum(k => k.TransactionAmount) seems to be not working.
var transactions = new List<Transaction>()
{
new Transaction() {
TransactionAmount = 3334.38,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 20),
TransactionType = TransactionType.Deposit
},
new Transaction() {
TransactionAmount = -3334.38,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 21),
TransactionType = TransactionType.Withdraw
},
new Transaction() {
TransactionAmount = 1000.23,
TransactionDate = new DateTime(DateTime.UtcNow.AddYears(-2).Year, 1, 25),
TransactionType = TransactionType.Deposit
},
};
var data = from t in transactions
group t by new {t.TransactionDate.Year , t.TransactionDate.Month} into g
select new {
tBalance = g.Sum(x => x.TransactionAmount),
g.First().TransactionDate.Month,
g.First().TransactionDate.Year
};
// ----------------------
// result :
// tBalance 1000.23
// Month 1
// Year 2018

Insert Data into middle of range

I have the following model
public class DailyRoutine
{
public int Id { get; set; }
public DateTime Date { get; set; }
public string Description { get; set; }
}
Scenario:
When is created at initial time with 5 records which means 5 entries are entered for each day. Take an example May 1 to May 5 of 2017. Description have any string.
User can add a new record in the middle so that the following records should be moved and changed to next days.
Expected Output:
Example, user can give a date and description in input and submit. If the input date is '5/3/2017' (May 3), the entry should be added after May 2 record and the existing May 3 record changed to May 4, May 4 to May 5 etc. So the out is like May 1 to May 6 and the given input is updated on May 3.
Please help me to this with out degrading performance
This approach will work:
List<DailyRoutine> d = new List<DailyRoutine>()
{
new DailyRoutine() { Date = new DateTime(2017, 7, 1)},
new DailyRoutine() { Date = new DateTime(2017, 7, 2)},
new DailyRoutine() { Date = new DateTime(2017, 7, 3)},
new DailyRoutine() { Date = new DateTime(2017, 7, 4)},
new DailyRoutine() { Date = new DateTime(2017, 7, 5)}
};
DailyRoutine newDr = new DailyRoutine() { Date = new DateTime(2017, 7, 2) };
DailyRoutine oldDr = d.Where(dr => dr.Date == newDr.Date).FirstOrDefault();
if (oldDr != null)
{
int idx = d.IndexOf(oldDr);
List<DailyRoutine> changeList = d.Where((dr, i) => i >= idx).ToList();
foreach (DailyRoutine i in changeList)
{
i.Date = i.Date.AddDays(1);
}
d.Insert((int)idx, newDr);
}
else
{
d.Add(newDr);
}

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();
}

Linq query to do group by and min

I have a datastructure as shown below. For each (Person, Game) pairs, I need to find the lastest score in the past 24 hours. Is it possible to do that in LINQ? Something like (Person, Game, LatestScore)
+----------------+-----------------+---------------+-+------------+
| Person | Game | Score |EventTime |
+-----------------------------------------------------------------+
| | | | |
| | | | |
+----------------+-----------------+---------------+--------------+
Any hints would be very helpful.
Assuming you have a class like this:
class GameInfo
{
public DateTime EventTime { get; set; }
public String Game { get; set; }
public String Person { get; set; }
public double Score { get; set; }
}
You could do:
List<GameInfo> data = new List<GameInfo> {
new GameInfo { Game = "G1", Person = "A", Score = 10, EventTime = new DateTime(2014, 10, 10, 10, 10, 10) },
new GameInfo { Game = "G1", Person = "A", Score = 10, EventTime = new DateTime(2014, 10, 10, 10, 10, 10) },
new GameInfo { Game = "G2", Person = "B", Score = 11, EventTime = new DateTime(2014, 10, 10, 20, 10, 10) },
new GameInfo { Game = "G2", Person = "B", Score = 11, EventTime = new DateTime(2014, 10, 10, 20, 10, 10) }
};
var q = from game in data
group game by new { G = game.Game, P = game.Person } into g
select new {
Person = g.Key.P,
Game = g.Key.G,
Score = g.Aggregate((curmin, x) => (curmin == null || (x.EventTime) < curmin.EventTime ? x : curmin)).Score
};
foreach (var item in q)
{
Console.WriteLine("{0}, {1}, {2}", item.Game, item.Person, item.Score);
}
As #Rawling pointed out, getting the scoce can be quite costly if you have lage data sets. So doing that efficiently might save alot of time in getting the desired output.
var dt = DateTime.Now.AddDays(-1);
var results = context.Where(x=>x.EventTime >= dt)
.GroupBy(x=>new {x.Person,x.Game})
.Select(x=>new
{
x.Key.Person,
x.Key.Game,
LatestScore = x.Where(d=>d.EventTime == x.Max(l=>l.EventTime))
.Select(d=>d.Score)
.FirstOrDefault()
});

Categories

Resources