Get latest record and group with highest date - LINQ - c#

I have table Billing as below
AccoundID | Group | DateOfBill
1234 | A | 2017-07-12
1234 | B | 2017-07-16
1234 | C | 2017-07-31
1235 | A | 2017-07-31
1236 | B | 2017-07-31
As you see, AccountID 1234 have made 3 transaction on July 2017. So I need a list where the AccountID 1234 must be in Group C because that's is latest date on that transaction.
Here is my code snippet
var LatestAccount = from n in Billing
where (n.Group == "A" || n.Group == "B" || n.Group == "C")
group n by new { n.AccountID, n.Group } into g
select new {
AccountId = g.Key.AccountID,
Group = g.Key.Group ,
DateOfBill = g.Max(t => t.DateOfBill)
};
But the result is wrong. How to do in LINQ?

class Program
{
static void Main(string[] args)
{
List<Billing> Billings = new List<Billing>()
{
new Billing()
{
AccountID = 1234, DateOfBill = new DateTime(2017,07,12), Group = "A"
},
new Billing()
{
AccountID = 1234, DateOfBill = new DateTime(2017,07,16), Group = "B"
},
new Billing()
{
AccountID = 1234, DateOfBill = new DateTime(2017,07,31), Group = "C"
},
new Billing()
{
AccountID = 1235, DateOfBill = new DateTime(2017,07,31), Group = "A"
},
new Billing()
{
AccountID = 1236, DateOfBill = new DateTime(2017,07,31), Group = "B"
}
};
var LatestAccount = from n in Billings
where (n.Group == "A" || n.Group == "B" || n.Group == "C")
group n by new { n.AccountID } into g
select g.Where(d => d.DateOfBill == g.Max(m => m.DateOfBill)).Select(x => new { AccountId = g.Key.AccountID, Group = x.Group, DateOfBill = x.DateOfBill }).FirstOrDefault();
foreach (var item in LatestAccount)
{
Console.WriteLine("AccountID: " + item.AccountId + " Date of Bill: " + item.DateOfBill + " Group: "+ item.Group);
}
Console.ReadLine();
}
}
class Billing
{
public int AccountID { get; set; }
public string Group { get; set; }
public DateTime DateOfBill { get; set; }
}
Is below what you want?
If you show me the output you want, I can modify my answer.

Related

How can I achieve SQL CASE statement from LINQ

I am new to LINQ and I would like to know if I can achieve the below SQL query from LINQ?
I am using Entity Framework Core.
SELECT 0 [All], [Range] =
CASE
WHEN Value BETWEEN 0 AND 25 THEN 'Low'
WHEN Value BETWEEN 25 AND 75 THEN 'Medium'
WHEN Value BETWEEN 75 AND 90 THEN 'High'
WHEN Value BETWEEN 90 AND 100 THEN 'Very High'
END
FROM Result.Calculation C
INNER JOIN Data.SampleSet S ON C.SampleSetID = S.ID
WHERE S.SampleDrawn >= DATEADD(MONTH,-3,GETDATE()) AND S.Department = 'LOCATION A'
Currently, I am using FromSql as below to call a stored procedure. I would like to know whether I can do the same without using stored procedures?
var result = context.MyData.FromSql("data.GetMyData #pType = {0}, #pLocation = {1}, #pNoOfDays = {2}, #pStartDate = {3}, #pEndDate = {4}", type, location, noOfDays, startDate, endDate).ToList();
Thanks.
You can use this.
from C in Calculations
join S in SampleSets on C.SampleSetID equals S.ID
where S.SampleDrawn >= DateTime.Now.AddMonths(-3)
&& S.Department == "LOCATION A"
select new {
All = 1
, Range =
(C.Value >= 0 && C.Value < 25) ? "Low" :
(C.Value >= 25 && C.Value < 75) ? "Medium" :
(C.Value >= 75 && C.Value < 90) ? "High" :
(C.Value >= 90 && C.Value <= 100) ? "Very High" : null
}
You can use this if it suits you.
I would just explain the LINQ query part.
You can use this with EF. I created dummy data for these.
For EF, use IQueryable instead.
// from a row in first table
// join a row in second table
// on a.Criteria equal to b.Criteria
// where additional conditions
// select the records into these two fields called All and Range
// Convert the result set to list.
var query = (from a in lstCalc
join b in lstSampleSet
on a.SampleSetID equals b.ID where b.SampleDrawn >= DateTime.Now.AddMonths(-8)
&& b.Department == "Location A"
select new { All = 0, Range = Utilities.RangeProvider(a.Value) }).ToList();
EDIT : LINQ Query for grouped result.. Make sure you are using IQueryable.
var query = (from a in lstCalc
join b in lstSampleSet
on a.SampleSetID equals b.ID where b.SampleDrawn >= DateTime.Now.AddMonths(-8)
&& b.Department == "Location A"
group a by Utilities.RangeProvider(a.Value) into groupedData
select new Result { All = groupedData.Sum(y => y.Value), Range =
groupedData.Key }).ToList();
Here is the code for the same.
public class Program
{
public static void Main(string[] args) {
List<Calculation> lstCalc = new List<Calculation>();
lstCalc.Add(new Calculation() {SampleSetID=1, Value=10 });
lstCalc.Add(new Calculation() { SampleSetID = 1, Value = 10 });
lstCalc.Add(new Calculation() { SampleSetID = 2, Value = 20 });
lstCalc.Add(new Calculation() { SampleSetID = 3, Value = 30 });
lstCalc.Add(new Calculation() { SampleSetID = 4, Value = 40 });
lstCalc.Add(new Calculation() { SampleSetID = 5, Value = 50 });
lstCalc.Add(new Calculation() { SampleSetID = 6, Value = 60 });
lstCalc.Add(new Calculation() { SampleSetID = 7, Value = 70 });
lstCalc.Add(new Calculation() { SampleSetID = 8, Value = 80 });
lstCalc.Add(new Calculation() { SampleSetID = 9, Value = 90 });
List<SampleSet> lstSampleSet = new List<SampleSet>();
lstSampleSet.Add(new SampleSet() {Department = "Location A", ID=1, SampleDrawn=DateTime.Now.AddMonths(-5)});
lstSampleSet.Add(new SampleSet() { Department = "Location A", ID = 2, SampleDrawn = DateTime.Now.AddMonths(-4) });
lstSampleSet.Add(new SampleSet() { Department = "Location A", ID = 3, SampleDrawn = DateTime.Now.AddMonths(-3) });
lstSampleSet.Add(new SampleSet() { Department = "Location A", ID = 4, SampleDrawn = DateTime.Now.AddMonths(-2) });
lstSampleSet.Add(new SampleSet() { Department = "Location A", ID = 5, SampleDrawn = DateTime.Now.AddMonths(-2) });
lstSampleSet.Add(new SampleSet() { Department = "Location A", ID = 6, SampleDrawn = DateTime.Now.AddMonths(-2) });
lstSampleSet.Add(new SampleSet() { Department = "Location A", ID = 7, SampleDrawn = DateTime.Now.AddMonths(-1) });
var query = (from a in lstCalc
join b in lstSampleSet
on a.SampleSetID equals b.ID where b.SampleDrawn >= DateTime.Now.AddMonths(-8)
&& b.Department == "Location A"
select new { All = 0, Range = Utilities.RangeProvider(a.Value) }).ToList();
Console.WriteLine(query.Count);
Console.ReadLine();
}
}
public class Utilities
{
public static string RangeProvider(int value)
{
if (value > 0 && value <= 25)
{ return "Low"; }
if (value > 25 && value <= 75)
{ return "Medium"; }
if (value > 75 && value <= 90)
{ return "High"; }
else
{ return "Very High"; }
}
}
public class Result {
public int All { get; set; }
public string Range { get; set; }
}
public class Calculation
{
public int SampleSetID { get; set; }
public int Value { get; set; }
}
public class SampleSet
{
public int ID { get; set; }
public DateTime SampleDrawn { get; set; }
public string Department { get; set; }
}
Below is the final LINQ statement which worked for me. As Amit explain in his answer RangeProvider method will be used to replace the SQL CASE statement.
var test2 = (from a in context.Calculations
join b in context.SampleSets on a.SampleSetID equals b.ID
where b.SampleDrawn >= DateTime.Now.AddDays(-10) && b.Department == "Location A"
group a by RangeProvider(a.Value) into groupedData
select new { All = groupedData.Count(), Range = groupedData.Key });

Get the last record of group using LINQ

I have a list:
var list = new List<Foo>() {
new Foo() { FooId = 1, GroupId = 1, ValueA = 3},
new Foo() { FooId = 2,GroupId = 1, ValueA = 40},
new Foo() { FooId = 3,GroupId = 2, ValueA = 80},
new Foo() { FooId = 4, GroupId = 2, ValueA = 20},
};
I want to just get the latest record so the result would be like:
| GroupId | ValueA |
|---------|--------|
| 1 | 40 |
| 2 | 20 |
Depending on what you want you can change OrderBy or even can use First or Last in this.
list
.OrderByDescending(a => a.ValueA)
.GroupBy(a => a.GroupId)
.Select(g => g.Last());
var result =
( from p in List
group p by p.Value into g
select new {
GroupId = g.OrderByDescending(x=>x.Id).FirstOrDefault().GroupId,
ValueA = g.OrderByDescending(x => x.Id).FirstOrDefault().ValueA
}
).ToList();
It works for me...
So you can try.
Is this what you want to do?
var result1 = from p in list
group p by p.GroupId into g
select new { GroupId = g.Key, MaxFooId = g.FooId.Max() };
var result2 = from p in result1
join list q on p.MaxFooId equals q.FooId
select new { GroupId = p.GroupId, ValueA = q.ValueA }

Linq Union does not work

I have a two lists rca and purchase as follow.
List<GroupDate> rca = (from sold in GetSoldOut
group sold by new { sold.CreatedDate, sold.SubCategoryID }
into g
select new GroupDate
{
Date = g.Key.CreatedDate,
SubCategoryID = g.Key.SubCategoryID,
Count = g.Count()
}).ToList();
and
List<GroupDate> purchase = (from sold in stock
group sold by new { sold.CreatedDate, sold.SubCategoryID }
into g
select new GroupDate
{
Date = g.Key.CreatedDate,
SubCategoryID = g.Key.SubCategoryID,
Count = g.Sum(a => a.Quantity)
}).ToList();
And Join this two lists as follow.
var leftOuterJoinRP = from first in replace
join last in prepaid
on new { first.Date, first.SubCategoryID } equals new { last.Date, last.SubCategoryID }
into temp
from last in temp.DefaultIfEmpty(new GroupDate { })
select new CardBalance
{
Date = first.Date,
SubCategoryID = first.SubCategoryID,
ReDemage = first.Count,
Prepaid = last.Count
};
var rightOuterJoinRP = from last in prepaid
join first in replace
on new { last.Date, last.SubCategoryID } equals new { first.Date, first.SubCategoryID }
into temp
from first in temp.DefaultIfEmpty(new GroupDate { })
select new CardBalance
{
Date = last.Date,
SubCategoryID = last.SubCategoryID,
ReDemage = first.Count,
Prepaid = last.Count
};
leftOuterJoinRP contains
Date---| Balance | OpeningStock | Prepaid | Purchase | RCA | Demage | SubCategoryId
1/1/17 | 0-------| 0----------- | 1------ | 600 -----| 2-- | 0 ---- | 84
and
rightOuterJoinRP contains
Date---| Balance | OpeningStock | Prepaid | Purchase | RCA | Demage | SubCategoryId
1/1/17 | 0-------| 0----------- | 1------ | 600-----| 2-- | 0 ---- | 84
1/2/17 | 0-------| 0----------- | 1------ | 110-----| 1-- | 0 ---- | 84
Union leftOuterJoinRP and rightOuterJoinRP as follow.
var fullOuterJoinRP = leftOuterJoinRP.Union(rightOuterJoinRP);
But it does not union. fullOuterJoinRP get all rows.
You need to use the Union method which takes an IEqualityComparer<T> parameter.
Let's say you have a TestClass
public class TestClass
{
public int TestInteger { get; set; }
public string TestString { get; set; }
}
And create two lists
List<TestClass> list1 = new List<TestClass>();
list1.Add(new TestClass() { TestInteger = 1, TestString = "t1" });
list1.Add(new TestClass() { TestInteger = 2, TestString = "t2" });
List<TestClass> list2 = new List<TestClass>();
list2.Add(new TestClass() { TestInteger = 1, TestString = "t1" });
list2.Add(new TestClass() { TestInteger = 3, TestString = "t3" });
IEnumerable<TestClass> list3 = list1.Union(list2);
Here, the Union method will return all four objects, like in your question.
The Union method needs an IEqualityComparer<TestClass> parameter to compare the objects.
public class TestClassComparer : IEqualityComparer<TestClass>
{
public bool Equals(TestClass x, TestClass y)
{
//Check whether the objects are the same object.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether the class properties are equal.
return x != null && y != null && x.TestInteger.Equals(y.TestInteger) && x.TestString.Equals(y.TestString);
}
public int GetHashCode(TestClass obj)
{
//Get hash code for the TestString field if it is not null.
int hashTestString = obj.TestString == null ? 0 : obj.TestString.GetHashCode();
//Get hash code for the TestInteger field.
int hashTestInteger = obj.TestInteger.GetHashCode();
//Calculate the hash code for the TestClass object.
return hashTestString ^ hashTestInteger;
}
}
Now, if you call
IEnumerable<TestClass> list3 = list1.Union(list2, new TestClassComparer());
The resulting list3 will have three unique objects.

LINQ Join usage data from two data sets into one

What I have:
Two lists of the following model:
int SubscriptionId
int ItemId
double Usage
double EffectiveRate
string ResourceName
string UnitOfMeasure
The first contains usage data of the last month like this:
SubscriptionId ItemId Usage EffectiveRate ResourceName UnitOfMesaure
_________________________________________________________________________
1 1 2 2,75 R1 U1
1 2 3 1,50 R2 U2
The seconds contains usage data of the current month like this:
SubscriptionId ItemId Usage EffectiveRate ResourceName UnitOfMesaure
_________________________________________________________________________
1 1 5 2,75 R1 U1
1 3 2 1,50 R3 U3
What I want:
This should be merge in a list like this:
SubscriptionId ItemId UsageThis UsageLast EffRate ResName UOM
_________________________________________________________________________
1 1 5 2 2,75 R1 U1
1 2 0 3 1,50 R2 U2
1 3 2 0 1,50 R3 U3
What I have:
//data for both months available
if (resourcesThisMonth.Any() && resourcesLastMonth.Any())
{
//join both months
resources = from resourceLastMonth in resourcesLastMonth
join resourceThisMonth in resourcesThisMonth
on new { resourceLastMonth.SubscriptionId, resourceLastMonth.ItemId } equals new { resourceThisMonth.SubscriptionId, resourceThisMonth.ItemId }
select new Resource
{
SubscriptionId = resourceThisMonth.SubscriptionId,
ItemId = resourceThisMonth.ItemId,
UsageThisMonth = resourceThisMonth.Usage,
UsageLastMonth = resourceLastMonth.Usage,
EffectiveRate = resourceThisMonth.EffectiveRate,
ResourceName = resourceThisMonth.ResourceName,
UnitOfMeasure = resourceThisMonth.UnitOfMeasure
};
//resources only last month available
var resourcesOnlyLastMonth = resourcesLastMonth.Where(r => !resourcesThisMonth.Where(s => s.ItemId == r.ItemId && s.SubscriptionId == r.SubscriptionId).Any())
.Select(r => new Resource
{
SubscriptionId = r.SubscriptionId,
ItemId = r.ItemId,
UsageThisMonth = 0.0,
UsageLastMonth = r.Units,
EffectiveRate = r.EffectiveRate,
ResourceName = r.ResourceName,
UnitOfMeasure = r.UnitOfMeasure
});
//resources only this month available
var resourcesOnlyThisMonth = resourcesThisMonth.Where(r => !resourcesLastMonth.Where(s => s.ItemId == r.ItemId && s.SubscriptionId == r.SubscriptionId).Any())
.Select(r => new Resource
{
SubscriptionId = r.SubscriptionId,
ItemId = r.ItemId,
UsageThisMonth = r.Usage,
UsageLastMonth = 0.0,
EffectiveRate = r.EffectiveRate,
ResourceName = r.ResourceName,
UnitOfMeasure = r.UnitOfMeasure
});
//union data
resources = resources.Union(resourcesOnlyLastMonth);
resources = resources.Union(resourcesOnlyThisMonth);
}
//data for last month available
else if (resourcesLastMonth.Any())
{
resources = from resource in resourcesLastMonth
select new Resource
{
SubscriptionId = resource.SubscriptionId,
ItemId = resource.ItemId,
UsageThisMonth = 0.0,
UsageLastMonth = resource.Usage,
EffectiveRate = resource.EffectiveRate,
ResourceName = resource.ResourceName,
UnitOfMeasure = resource.UnitOfMeasure
};
}
//data for this month available
else if (resourcesThisMonth.Any())
{
resources = from resource in resourcesThisMonth
select new Resource
{
SubscriptionId = resource.SubscriptionId,
ItemId = resource.ItemId,
UsageThisMonth = resource.Usage,
UsageLastMonth = 0.0,
EffectiveRate = resource.EffectiveRate,
ResourceName = resource.ResourceName,
UnitOfMeasure = resource.UnitOfMeasure
};
}
//no data available
else
{
resources = new List<Resource>();
}
Problem:
This is very much code - should be less, any possible solutions failed so far
Thanks for helping!
public class ExampleClass
{
public int Id1 { get; set; }
public int Id2 { get; set; }
public int Usage { get; set; }
public int UsageThis { get; set; }
public int UsageLast { get; set; }
}
List<ExampleClass> listThisMonth = new List<ExampleClass>
{
new ExampleClass{Id1=1, Id2=1,Usage=7, UsageThis=1, UsageLast=0},
new ExampleClass{Id1=2, Id2=2,Usage=4, UsageThis=2, UsageLast=0},
new ExampleClass{Id1=3, Id2=3,Usage=1, UsageThis=3, UsageLast=0},
};
List<ExampleClass> listLastMonth = new List<ExampleClass>
{
new ExampleClass{Id1=1, Id2=1,Usage=3, UsageThis=1, UsageLast=1},
new ExampleClass{Id1=4, Id2=4,Usage=3, UsageThis=4, UsageLast=3},
new ExampleClass{Id1=2, Id2=2,Usage=1, UsageThis=8, UsageLast=6},
};
var result = listThisMonth.Select(a=>new {value=a, list=1})
.Union(listLastMonth.Select(a => new { value = a, list = 2 }))
.GroupBy(a => new { Id1 = a.value.Id1, Id2 = a.value.Id2 })
.Select(x => new ExampleClass
{
Id1 = x.Key.Id1,
Id2 = x.Key.Id2,
UsageThis = x.Any(o => o.list == 1) ? x.First(o => o.list == 1).value.Usage : 0,
UsageLast = x.Any(o => o.list == 2) ? x.First(o => o.list == 2).value.Usage : 0,
Usage = x.Sum(o=>o.value.Usage)
}).ToList();
//id1 id2 current last sum
//1 1 7 3 10
//2 2 4 1 5
//3 3 1 0 1
//4 4 0 3 3
It looks to me that what you're looking for is a full outer join. Unfortunately, it looks like LINQ doesn't have a construct for that. So, there are a few options: LINQ - Full Outer Join
For your scenario, it looks like you have some redundant code. You should be able to do the union using two outer joins to get the correct result set. For example:
// Left join the current month with the last month
var currentMonth =
from current in resourcesThisMonth
join last in resourcesLastMonth on new { current.SubscriptionId, current.ItemId } equals new { last.SubscriptionId, last.ItemId } into outer
from o in outer.DefaultIfEmpty()
select new Resource
{
SubscriptionId = current.SubscriptionId,
ItemId = current.ItemId,
UnitsThisMonth = current.Units,
UnitsLastMonth = o?.Units ?? 0, // Replace NULL with 0
EffectiveRate = current.EffectiveRate,
ResourceName = current.ResourceName,
UnitOfMeasure = current.UnitOfMeasure
};
// Reverse of the first join. Last month LEFT JOIN Current month
var lastMonth =
from last in resourcesLastMonth
join current in resourcesThisMonth on new { last.SubscriptionId, last.ItemId } equals new { current.SubscriptionId, current.ItemId } into outer
from o in outer.DefaultIfEmpty()
select new Resource
{
SubscriptionId = last.SubscriptionId,
ItemId = last.ItemId,
UnitsThisMonth = o?.Units ?? 0, // Replace NULL with 0
UnitsLastMonth = last.Units,
EffectiveRate = o?.EffectiveRate ?? last.EffectiveRate,
ResourceName = o?.ResourceName ?? last.ResourceName,
UnitOfMeasure = o?.UnitOfMeasure ?? last.UnitOfMeasure
};
// Union them together to get a full join
var resources = currentMonth.Union(lastMonth);

Pivot data using LINQ

I have a collection of items that contain an Enum (TypeCode) and a User object, and I need to flatten it out to show in a grid. It's hard to explain, so let me show a quick example.
Collection has items like so:
TypeCode | User
---------------
1 | Don Smith
1 | Mike Jones
1 | James Ray
2 | Tom Rizzo
2 | Alex Homes
3 | Andy Bates
I need the output to be:
1 | 2 | 3
Don Smith | Tom Rizzo | Andy Bates
Mike Jones | Alex Homes |
James Ray | |
I've tried doing this using foreach, but I can't do it that way because I'd be inserting new items to the collection in the foreach, causing an error.
Can this be done in Linq in a cleaner fashion?
I'm not saying it is a great way to pivot - but it is a pivot...
// sample data
var data = new[] {
new { Foo = 1, Bar = "Don Smith"},
new { Foo = 1, Bar = "Mike Jones"},
new { Foo = 1, Bar = "James Ray"},
new { Foo = 2, Bar = "Tom Rizzo"},
new { Foo = 2, Bar = "Alex Homes"},
new { Foo = 3, Bar = "Andy Bates"},
};
// group into columns, and select the rows per column
var grps = from d in data
group d by d.Foo
into grp
select new {
Foo = grp.Key,
Bars = grp.Select(d2 => d2.Bar).ToArray()
};
// find the total number of (data) rows
int rows = grps.Max(grp => grp.Bars.Length);
// output columns
foreach (var grp in grps) {
Console.Write(grp.Foo + "\t");
}
Console.WriteLine();
// output data
for (int i = 0; i < rows; i++) {
foreach (var grp in grps) {
Console.Write((i < grp.Bars.Length ? grp.Bars[i] : null) + "\t");
}
Console.WriteLine();
}
Marc's answer gives sparse matrix that can't be pumped into Grid directly.
I tried to expand the code from the link provided by Vasu as below:
public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>(
this IEnumerable<TSource> source
, Func<TSource, TKey1> key1Selector
, Func<TSource, TKey2> key2Selector
, Func<IEnumerable<TSource>, TValue> aggregate)
{
return source.GroupBy(key1Selector).Select(
x => new
{
X = x.Key,
Y = source.GroupBy(key2Selector).Select(
z => new
{
Z = z.Key,
V = aggregate(from item in source
where key1Selector(item).Equals(x.Key)
&& key2Selector(item).Equals(z.Key)
select item
)
}
).ToDictionary(e => e.Z, o => o.V)
}
).ToDictionary(e => e.X, o => o.Y);
}
internal class Employee
{
public string Name { get; set; }
public string Department { get; set; }
public string Function { get; set; }
public decimal Salary { get; set; }
}
public void TestLinqExtenions()
{
var l = new List<Employee>() {
new Employee() { Name = "Fons", Department = "R&D", Function = "Trainer", Salary = 2000 },
new Employee() { Name = "Jim", Department = "R&D", Function = "Trainer", Salary = 3000 },
new Employee() { Name = "Ellen", Department = "Dev", Function = "Developer", Salary = 4000 },
new Employee() { Name = "Mike", Department = "Dev", Function = "Consultant", Salary = 5000 },
new Employee() { Name = "Jack", Department = "R&D", Function = "Developer", Salary = 6000 },
new Employee() { Name = "Demy", Department = "Dev", Function = "Consultant", Salary = 2000 }};
var result5 = l.Pivot3(emp => emp.Department, emp2 => emp2.Function, lst => lst.Sum(emp => emp.Salary));
var result6 = l.Pivot3(emp => emp.Function, emp2 => emp2.Department, lst => lst.Count());
}
* can't say anything about the performance though.
You can use Linq's .ToLookup to group in the manner you are looking for.
var lookup = data.ToLookup(d => d.TypeCode, d => d.User);
Then it's a matter of putting it into a form that your consumer can make sense of. For instance:
//Warning: untested code
var enumerators = lookup.Select(g => g.GetEnumerator()).ToList();
int columns = enumerators.Count;
while(columns > 0)
{
for(int i = 0; i < enumerators.Count; ++i)
{
var enumerator = enumerators[i];
if(enumator == null) continue;
if(!enumerator.MoveNext())
{
--columns;
enumerators[i] = null;
}
}
yield return enumerators.Select(e => (e != null) ? e.Current : null);
}
Put that in an IEnumerable<> method and it will (probably) return a collection (rows) of collections (column) of User where a null is put in a column that has no data.
I guess this is similar to Marc's answer, but I'll post it since I spent some time working on it. The results are separated by " | " as in your example. It also uses the IGrouping<int, string> type returned from the LINQ query when using a group by instead of constructing a new anonymous type. This is tested, working code.
var Items = new[] {
new { TypeCode = 1, UserName = "Don Smith"},
new { TypeCode = 1, UserName = "Mike Jones"},
new { TypeCode = 1, UserName = "James Ray"},
new { TypeCode = 2, UserName = "Tom Rizzo"},
new { TypeCode = 2, UserName = "Alex Homes"},
new { TypeCode = 3, UserName = "Andy Bates"}
};
var Columns = from i in Items
group i.UserName by i.TypeCode;
Dictionary<int, List<string>> Rows = new Dictionary<int, List<string>>();
int RowCount = Columns.Max(g => g.Count());
for (int i = 0; i <= RowCount; i++) // Row 0 is the header row.
{
Rows.Add(i, new List<string>());
}
int RowIndex;
foreach (IGrouping<int, string> c in Columns)
{
Rows[0].Add(c.Key.ToString());
RowIndex = 1;
foreach (string user in c)
{
Rows[RowIndex].Add(user);
RowIndex++;
}
for (int r = RowIndex; r <= Columns.Count(); r++)
{
Rows[r].Add(string.Empty);
}
}
foreach (List<string> row in Rows.Values)
{
Console.WriteLine(row.Aggregate((current, next) => current + " | " + next));
}
Console.ReadLine();
I also tested it with this input:
var Items = new[] {
new { TypeCode = 1, UserName = "Don Smith"},
new { TypeCode = 3, UserName = "Mike Jones"},
new { TypeCode = 3, UserName = "James Ray"},
new { TypeCode = 2, UserName = "Tom Rizzo"},
new { TypeCode = 2, UserName = "Alex Homes"},
new { TypeCode = 3, UserName = "Andy Bates"}
};
Which produced the following results showing that the first column doesn't need to contain the longest list. You could use OrderBy to get the columns ordered by TypeCode if needed.
1 | 3 | 2
Don Smith | Mike Jones | Tom Rizzo
| James Ray | Alex Homes
| Andy Bates |
#Sanjaya.Tio I was intrigued by your answer and created this adaptation which minimizes keySelector execution. (untested)
public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>(
this IEnumerable<TSource> source
, Func<TSource, TKey1> key1Selector
, Func<TSource, TKey2> key2Selector
, Func<IEnumerable<TSource>, TValue> aggregate)
{
var lookup = source.ToLookup(x => new {Key1 = key1Selector(x), Key2 = key2Selector(x)});
List<TKey1> key1s = lookup.Select(g => g.Key.Key1).Distinct().ToList();
List<TKey2> key2s = lookup.Select(g => g.Key.Key2).Distinct().ToList();
var resultQuery =
from key1 in key1s
from key2 in key2s
let lookupKey = new {Key1 = key1, Key2 = key2}
let g = lookup[lookupKey]
let resultValue = g.Any() ? aggregate(g) : default(TValue)
select new {Key1 = key1, Key2 = key2, ResultValue = resultValue};
Dictionary<TKey1, Dictionary<TKey2, TValue>> result = new Dictionary<TKey1, Dictionary<TKey2, TValue>>();
foreach(var resultItem in resultQuery)
{
TKey1 key1 = resultItem.Key1;
TKey2 key2 = resultItem.Key2;
TValue resultValue = resultItem.ResultValue;
if (!result.ContainsKey(key1))
{
result[key1] = new Dictionary<TKey2, TValue>();
}
var subDictionary = result[key1];
subDictionary[key2] = resultValue;
}
return result;
}

Categories

Resources