Joining & Transposing multiple Lists using LINQ - c#

Hi I have the following code which returns me the right data but it seems there must be a better way to combine 3 lists, based on their common field(s) and transpose the results out into a new list of a given type using LINQ, instead of resorting to the foreach at the end. Any ideas?
public IEnumerable<StagSummaryByCflHistoricalItem> GetSummaryByCflHistorical(DateTime currentDate)
{
var allRecords =
this.preGrantSummaryHistoricalRepository
.AllWithFetch(this.preGrantSummaryHistoricalRepository.All, x => x.CaseFileLocation)
.Where(
x => x.Date >= currentDate.FirstDayOfQuarterFromDateTime()
&& x.Date <= currentDate.LastDayOfQuarterFromDateTime())
.ToList();
var summaryForQuarter =
allRecords.GroupBy(x => new { x.CaseFileLocation.Id, x.CaseFileLocation.Name }).Select(
x =>
new
{
CaseFileLocationId = x.Key.Id,
Description = x.Key.Name,
TotalCasesEnteredCfl = x.Sum(y => y.TotalCasesEntered),
TotalNetFeeEnteredCfl = x.Sum(y => y.TotalNetFeeEntered),
TotalCasesLeftCfl = x.Sum(y => y.TotalCasesLeft),
TotalNetFeeLeftCfl = x.Sum(y => y.TotalNetFeeLeft)
})
.OrderBy(x => x.CaseFileLocationId)
.ToList();
var summaryForMonth =
allRecords.Where(x => x.Date >= currentDate.FirstDayOfMonthFromDateTime())
.GroupBy(x => new { x.CaseFileLocation.Id, x.CaseFileLocation.Name }).Select(
x =>
new
{
CaseFileLocationId = x.Key.Id,
Description = x.Key.Name,
TotalCasesEnteredCfl = x.Sum(y => y.TotalCasesEntered),
TotalNetFeeEnteredCfl = x.Sum(y => y.TotalNetFeeEntered),
TotalCasesLeftCfl = x.Sum(y => y.TotalCasesLeft),
TotalNetFeeLeftCfl = x.Sum(y => y.TotalNetFeeLeft)
})
.OrderBy(x => x.CaseFileLocationId)
.ToList();
var summaryForWeek =
allRecords.Where(x => x.Date >= currentDate.FirstDayOfWeekFromDateTime(DayOfWeek.Monday)).GroupBy(
x => new { x.CaseFileLocation.Id, x.CaseFileLocation.Name }).Select(
x =>
new
{
CaseFileLocationId = x.Key.Id,
Description = x.Key.Name,
TotalCasesEnteredCfl = x.Sum(y => y.TotalCasesEntered),
TotalNetFeeEnteredCfl = x.Sum(y => y.TotalNetFeeEntered),
TotalCasesLeftCfl = x.Sum(y => y.TotalCasesLeft),
TotalNetFeeLeftCfl = x.Sum(y => y.TotalNetFeeLeft)
})
.OrderBy(x => x.CaseFileLocationId)
.ToList();
var finalList = summaryForQuarter
.Select(x => new StagSummaryByCflHistoricalItem()
{
CaseFileLocationId = x.CaseFileLocationId,
Description = x.Description,
QuarterTotalCasesEnteredCfl = x.TotalCasesEnteredCfl,
QuarterTotalCasesLeftCfl = x.TotalCasesLeftCfl,
QuarterTotalNetFeeEnteredCfl = x.TotalNetFeeEnteredCfl,
QuarterTotalNetFeeLeftCfl = x.TotalNetFeeLeftCfl
})
.OrderBy(x => x.CaseFileLocationId)
.ToList();
foreach (var qrt in finalList)
{
var mnthData = summaryForMonth.FirstOrDefault(x => x.CaseFileLocationId == qrt.CaseFileLocationId);
if (mnthData != null)
{
qrt.MonthTotalCasesEnteredCfl = mnthData.TotalCasesEnteredCfl;
qrt.MonthTotalCasesLeftCfl = mnthData.TotalCasesLeftCfl;
qrt.MonthTotalNetFeeEnteredCfl = mnthData.TotalNetFeeEnteredCfl;
qrt.MonthTotalNetFeeLeftCfl = mnthData.TotalNetFeeLeftCfl;
}
var weekData = summaryForWeek.FirstOrDefault(x => x.CaseFileLocationId == qrt.CaseFileLocationId);
if (weekData == null)
{
continue;
}
qrt.WeekTotalCasesEnteredCfl = weekData.TotalCasesEnteredCfl;
qrt.WeekTotalCasesLeftCfl = weekData.TotalCasesLeftCfl;
qrt.WeekTotalNetFeeEnteredCfl = weekData.TotalNetFeeEnteredCfl;
qrt.WeekTotalNetFeeLeftCfl = weekData.TotalNetFeeLeftCfl;
}
return finalList;
}
Note: I am intentionally getting the entire quarter's worth of data first as a list and then operating on it to do the month & quarter totals but this is mainly because I cannot fathom a way to get the end result from a combined LINQ IQuerable.
I am using NHibernate, LINQ method syntax, the repository pattern and SQL Server 2008

If I'm reading your code correctly, you're projecting your results to anonymous types with the same resulting members, which makes it very easy to join your results together. I did something similar recently with Union. Here's a simplified example I just wrote to demonstrate:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace ConsoleApplication1 {
public class Month {
public int MonthID { get; set; }
public string MonthName { get; set; }
public int NoDays { get; set; }
}
internal class Program {
private static void Main(string[] args) {
// Build up months
var months = new List<Month>();
for (var i = 1; i <= 12; i++) {
months.Add(new Month {
MonthID = i,
MonthName = DateTimeFormatInfo.CurrentInfo.GetMonthName(i),
NoDays = DateTime.DaysInMonth(2012, i)
});
}
var w = months.Select(m => new {
m.MonthName
});
var x = months.Select(m => new {
m.MonthName
});
var y = months.Select(m => new {
m.MonthName
});
var z = w.Union(x).Union(y);
foreach (var m in z) {
Console.WriteLine(m.MonthName);
}
Console.Read();
}
}
}
Bear in mind that "Union" (like the SQL UNION clause) will remove any duplicates from your list. If you don't want to remove duplicates (i.e. perform a "union all"), use "Concat" as follows:
var z = w.Concat(x).Concat(y);

Related

Duplicated linq query

I have 2 almost identical linq queries and want to remove repeating code from it. The only difference is the extra property in the GroupBy depending on some true/false condition.
How can I conditionally group by in linq without repeating the code like below?
var allergensList = _context.RecipeAllergens
.Where(x => x.ParentId == Id && x.AllergenId != null)
.ToList();
var allergens = new List<AllergenInfo>();
if (isRecipe)
{
allergens = allergensList
.GroupBy(x => new { x.AllergenName, x.AllergenIcon, x.AllergenMaycontains })
.Select(a =>
{
var v = a.OrderBy(x => x.AllergenMaycontains).First();
return new AllergenInfo
{
AllergenName = v.AllergenName,
AllergenIcon = v.AllergenIcon,
AllergenMayContain = v.AllergenMaycontains ?? false
};
})
.ToList();
}
else
{
allergens = allergensList
.GroupBy(x => new { x.AllergenName, x.AllergenIcon })
.Select(a =>
{
var v = a.OrderBy(x => x.AllergenMaycontains).First();
return new AllergenInfo
{
AllergenName = v.AllergenName,
AllergenIcon = v.AllergenIcon,
AllergenMayContain = v.AllergenMaycontains ?? false
};
})
.ToList();
}
You can left grouping by x.AllergenMaycontains but under condition
allergens = allergensList
.GroupBy(x => new { x.AllergenName, x.AllergenIcon, AllergenMaycontains = isRecipe ? x.AllergenMaycontains : false })
.Select(a =>
{
var v = a.OrderBy(x => x.AllergenMaycontains).First();
return new AllergenInfo
{
AllergenName = v.AllergenName,
AllergenIcon = v.AllergenIcon,
AllergenMayContain = v.AllergenMaycontains ?? false
};
})
.ToList();

Linq-to-SQL query (AsEnumerable) on multiple tables

Here's a Linq-to-SQL query that uses only one table from my SQL Server database and works perfectly:
private void GetData()
{
DateTime d = DateTime.Now;
using (DataClasses1DataContext dc = new DataClasses1DataContext())
{
var qte = dc.ENTREES_STOCKS.AsEnumerable()
.Where(x => x.ENTSTK_LOT == lot)
.Where(x => x.ART_CODE == artCode)
.Where(x => x.ENTSTK_USER == null)
.Select(s => new
{
art = s.ART_CODE,
date = s.ENTSTK_DTENTREE,
numLot = s.ENTSTK_LOT,
pnet = s.ENTSTK_PNET,
nbu = s.ENTSTK_NBU
})
.GroupBy(g => new { g.art, g.date, g.numLot })
.Select(n => new
{
n.Key.art,
n.Key.date,
n.Key.numLot,
pnet = n.Sum(x => Math.Round(Convert.ToDecimal(x.pnet), 2)),
nbu = n.Sum(x => Math.Round(Convert.ToDecimal(x.nbu), 2)),
});
QEntreeTB.Text = qte.First().pnet.ToString();
NbuEntreeTB.Text = qte.First().nbu.ToString();
}
}
How could I modify this code to join other tables to this query like :
private void GetData()
{
DateTime d = DateTime.Now;
using (DataClasses1DataContext dc = new DataClasses1DataContext())
{
var qte = dc.ENTREES_STOCKS.AsEnumerable()
// Thoseline of codes of course doesn't work
join art in dc.FICHES_ARTICLES on ENTREES_STOCKS.ART_CODE equals art.ART_CODE
join ent in dc.STK_ENT on art.ART_CODE equals ent.ART_CODE
....
//
.Where(x => x.ENTSTK_LOT == lot)
.Where(x => x.ART_CODE == artCode)
.Where(x => x.ENTSTK_USER == null)
.Select(s =>
new
{
art = s.ART_CODE,
date = s.ENTSTK_DTENTREE,
numLot = s.ENTSTK_LOT,
pnet = s.ENTSTK_PNET,
nbu = s.ENTSTK_NBU
}
)
.GroupBy(g => new { g.art, g.date, g.numLot })
.Select(n =>
new
{
n.Key.art,
n.Key.date,
n.Key.numLot,
pnet = n.Sum(x => Math.Round(Convert.ToDecimal(x.pnet), 2)),
nbu = n.Sum(x => Math.Round(Convert.ToDecimal(x.nbu), 2)),
}
);
QEntreeTB.Text = qte.First().pnet.ToString();
NbuEntreeTB.Text = qte.First().nbu.ToString();
}
}
Or is there à way to code this query another way ??
Because in fact i just want to join multiples tables, groupby some fields and sum others fields.
Firstly, calling AsEnumerable is a bit redundent. Then you can simply use the Join extension method.
var qte = dc.ENTREES_STOCKS
.JOIN(dc.FICHES_ARTICLES,art=>art.ART_CODE, stock => stock.ART_CODE)
.JOIN(dc.STK_ENT,ent => ent.ART_CODE,stock => stock.ART_CODE)
.Where(x => x.ENTSTK_LOT == lot)
.Where(x => x.ART_CODE == artCode)
.Where(x => x.ENTSTK_USER == null)
....
You can find more answers here:
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/method-based-query-syntax-examples-join-operators

C# Linq nested GroupBy and Sum

I'm trying to translate a query I've written to Linq for the past few days I can't seem to make it work. This is the query I'm trying to translate:
SELECT
hsd.CoveragePeriodBeginDate,
RateTotal = SUM(hsd.Rate),
ReimbursementTotal = SUM(hsd.TotalReimbursement),
AdjustmentsTotal = SUM(hsd.Adjustments)
FROM
( SELECT
CoveragePeriodBeginDate,
PaidDate,
Rate = TotalClaimCharge,
TotalReimbursement = ReimbursementAmount,
Adjustments = SUM(BaseRateChangeAmount)
FROM
dbo.HsdMonthlyCapitatation
WHERE
MemberID = 12345678
GROUP BY
CoveragePeriodBeginDate,
PaidDate,
TotalClaimCharge,
ReimbursementAmount
) hsd
GROUP BY
hsd.CoveragePeriodBeginDate
ORDER BY
hsd.CoveragePeriodBeginDate
What I need to do is translate this into Linq. I have tried many different ways, but can't seem to make it work right. It always seems to aggregate too much.
Here's the closest I've come.
var rawCapData = db.HsdMonthlyCapitations.Where(x => x.MemberID == memberID)
.Select(x => new {
CoveragePeriod = x.CoveragePeriodBeginDate,
TotalCharge = x.TotalClaimCharge,
Reimbursement = x.ReimbursementAmount,
PaidDate = x.PaidDate,
Adjust = x.BaseRateChangeAmount
})
.GroupBy(x => new {
CoverageDate = x.CoveragePeriod,
Paid = x.PaidDate,
Rate = x.TotalCharge,
Reimburse = x.Reimbursement
})
.GroupBy(x => new {
Coverage = x.Key.CoverageDate,
DhsRate = x.Sum(y => y.TotalCharge),
ReimbursementTotal = x.Sum(y => y.Reimbursement),
Adjustments = x.Sum(y => y.Adjust)
})
.Select(x => new {
CapMonthYear = x.Key.Coverage,
DhsRate = x.Key.DhsRate,
TotalReimbursement = x.Key.ReimbursementTotal,
AdjustmentsTotal = x.Key.Adjustments
});
I should say I have gotten it to work, but I feel it's rather cludgey and a mix of regular LINQ and lambda expressions, and I would prefer to code it all with lambda expressions, if at all possible. Here's the code I have gotten to work:
var rawCapitationData = from capitation
in db.HsdMonthlyCapitations
where capitation.MemberID == memberID
group capitation by new
{
capitation.CoveragePeriodBeginDate,
capitation.TotalClaimCharge,
capitation.ReimbursementAmount,
capitation.PaidDate
} into cap
select new {
CapitationMonthYear = cap.Key.CoveragePeriodBeginDate,
TotalReimbursement = cap.Key.TotalClaimCharge,
DhsCapitationAmount = cap.Key.ReimbursementAmount,
PaidDate = cap.Key.PaidDate,
DhsAdjustments = cap.Sum(x => x.BaseRateChangeAmount)
};
var capitationData = rawCapitationData.GroupBy(cap => cap.CapitationMonthYear)
.Select(data => new {
CapitationDate = data.Key,
TotalReimbursement = data.Sum(x => x.TotalReimbursement),
DhsCapitationAmount = data.Sum(x => x.DhsCapitationAmount),
DhsAdjustments = data.Sum(x => x.DhsAdjustments)
});
My preference is to do this all in one statement. Is it even possible? I feel I'm close with the lambda expressions, but I know I'm missing something.
Any help or advice is greatly appreciated.
Not sure what are you trying to achieve, but I've ended up with this:
return db.HsdMonthlyCapitations
.Where(x => x.MemberID == memberID)
.GroupBy(x => new {x.CoveragePeriodBeginDate, x.PaidDate, x.TotalClaimCharge, x.ReimbursementAmount})
.Select(x => new
{
x.Key.CoveragePeriodBeginDate,
x.Key.PaidDate,
Rate = x.Key.TotalClaimCharge,
TotalReimbursement = x.Key.ReimbursementAmount,
Adjustments = x.Sum(m => m.BaseRateChangeAmount)
})
.GroupBy(x => x.CoveragePeriodBeginDate)
.Select(x => new
{
CoveragePeriodBeginDate = x.Key,
RateTotal = x.Sum(m => m.Rate),
ReimbursementTotal = x.Sum(m => m.TotalReimbursement),
AdjustmentsTotal = x.Sum(m => m.Adjustments),
})
.OrderBy(x => x.CoveragePeriodBeginDate);

linq-to-sql group by with count and custom object model

I'm looking to fill an object model with the count of a linq-to-sql query that groups by its key.
The object model looks somewhat like this:
public class MyCountModel()
{
int CountSomeByte1 { get; set; }
int CountSomeByte2 { get; set; }
int CountSomeByte3 { get; set; }
int CountSomeByte4 { get; set; }
int CountSomeByte5 { get; set; }
int CountSomeByte6 { get; set; }
}
This is what I have for the query:
var TheQuery = from x in MyDC.TheTable
where ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7
group x by x.SomeByte into TheCount
select new MyCountModel()
{
CountSomeByte1 = TheCount.Where(TheCount => TheCount.Key == 1)
.Select(TheCount).Count(),
CountSomeByte2 = TheCount.Where(TheCount => TheCount.Key == 2)
.Select(TheCount).Count(),
.....
CountSomeByte6 = TheCount.Where(TheCount => TheCount.Key == 6)
.Select(TheCount).Count(),
}.Single();
ListOfRecordIDs is list of longs that's passed in as a parameter. All the CountSomeByteN are underlined red. How do you do a count of grouped elements with the group's key mapped to an object model?
Thanks for your suggestions.
The select is taking each element of your group and projecting them to identical newly created MyCountModels, and you're only using one of them. Here's how I'd do it:
var dict = MyDC.TheTable
.Where(x => ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7)
.GroupBy(x => x.SomeByte)
.ToDictionary(grp => grp.Key, grp => grp.Count());
var result = new MyCountModel()
{
CountSomeByte1 = dict[1];
CountSomeByte2 = dict[2];
CountSomeByte3 = dict[3];
CountSomeByte4 = dict[4];
CountSomeByte5 = dict[5];
CountSomeByte6 = dict[6];
}
EDIT: Here's one way to do it in one statement. It uses an extension method called Into, which basically works as x.Into(f) == f(x). In this context, it can be viewed as like a Select that works on the whole enumerable rather than on its members. I find it handy for eliminating temporary variables in this sort of situation, and if I were to write this in one statement, it's probably how I'd do it:
public static U Into<T, U>(this T self, Func<T, U> func)
{
return func(self);
}
var result = MyDC.TheTable
.Where(x => ListOfRecordIDs.Contains(x.RecordID) && x.SomeByte < 7)
.GroupBy(x => x.SomeByte)
.ToDictionary(grp => grp.Key, grp => grp.Count())
.Into(dict => new MyCountModel()
{
CountSomeByte1 = dict[1];
CountSomeByte2 = dict[2];
CountSomeByte3 = dict[3];
CountSomeByte4 = dict[4];
CountSomeByte5 = dict[5];
CountSomeByte6 = dict[6];
});
Your range variable is not correct in the subqueries:
CountSomeByte6 = TheCount.Where(TheCount => TheCount.Key == 6)
.Select(TheCount).Count(),
In method notation you don't need the extra select:
CountSomeByte6 = TheCount.Where(theCount => theCount.Key == 6).Count(),
If you want to use it anyway:
CountSomeByte6 = TheCount.Where(theCount => theCount.Key == 6).Select(theCount => theCount).Count(),

Linq orderby, can't work out how to use it

I have this function:
/// <summary>
/// Return array of all badges for a users
/// </summary>
public static Badge[] getUserBadges(int UserID)
{
Badge[] ReturnBadges;
using (MainContext db = new MainContext())
{
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => c.BadgeID)
.Select(c => new { BadgeCount = c.Count(), TheBadge = c });
ReturnBadges = new Badge[q.Count()];
int i = 0;
foreach (var UserBadge in q)
{
ReturnBadges[i] = new Badge(UserBadge.TheBadge.Key);
ReturnBadges[i].Quantity = UserBadge.BadgeCount;
i++;
}
}
return ReturnBadges;
}
I wish to order by tblBadges.OrderID ascending but I can't seem to find out where to put it, can anyone help?
I've tried:
.OrderBy(c=> c.TheBadge.OrderID)
But it's not valid code. TheBadge.Key in the loop is a tblBadges type. It's confusing me a bit why intellisense wont let me do the order by anywhere!
TheBadge isn't a single badge, it's a group of badges... so I'd personally rename it if I were you. Now, which OrderId do you want to get? You've got multiple entities in the gruop. For example, you could do this:
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => c.BadgeID)
.Select(c => new { BadgeCount = c.Count(), TheBadge = c })
.OrderBy(x => x.TheBadge.First().OrderId);
That will order by some notional "first" element - although I don't know what the generated SQL will look like.
If you expect the OrderId to be the same for every badge with the same ID, you might use:
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => new { c.BadgeID, c.OrderID })
.OrderBy(group => group.Key.OrderID)
.Select(c => new { BadgeCount = c.Count(), TheBadge = c });
Try this:
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => c.BadgeID)
.Select(c => new { BadgeCount = c.Count(), TheBadge = c.Key }) // *mod
.OrderBy(c=> c.TheBadge.OrderID); // * added
In the following line, TheBadge is a linq collection, not the badge itself. You want c.Key.
.Select(c => new { BadgeCount = c.Count(), TheBadge = c })

Categories

Resources