Can someone explain me how i can use GroupBy with Select to get from this:
public class BadClass{
public int id; //can be grouped by id
public string name; //identical for the same Id
public bool flag; //identical for the same Id
public int bId;
public string bName;
public int cId;
public string cName;
}
This one: List <A> where:
public class A {
public int id;
public string name;
public bool flag;
public List<B> bList;
}
public class B {
public int id;
public string name;
public C c;
}
public class C {
public int id;
public int name;
}
I make example of data in BadClass:
List<BadClass> badClass = new List<BadClass>() {
new BadClass(){ id = 1, name = "A1", flag = true, bId = 1, bName = "B1", cId = 1, cName = "C1" },
new BadClass(){ id = 1, name = "A1", flag = true, bId = 2, bName = "B2", cId = 3, cName = "C2" },
new BadClass(){ id = 1, name = "A1", flag = true, bId = 3, bName = "B3", cId = 1, cName = "C1" },
new BadClass(){ id = 2, name = "A2", flag = false, bId = 4, bName = "B4", cId = 2, cName = "C2" },
new BadClass(){ id = 2, name = "A2", flag = false, bId = 5, bName = "B5", cId = 3, cName = "C3" }
};
/*
+----+------+-------+-----+-------+-----+-------+
| id | name | flag | bId | bName | cId | cName |
+----+------+-------+-----+-------+-----+-------+
| 1 | A1 | true | 1 | B1 | 1 | C1 |
+----+------+-------+-----+-------+-----+-------+
| 1 | A1 | true | 2 | B2 | 1 | C1 |
+----+------+-------+-----+-------+-----+-------+
| 1 | A1 | true | 3 | B3 | 3 | C3 |
+----+------+-------+-----+-------+-----+-------+
| 2 | A2 | false | 4 | B4 | 2 | C2 |
+----+------+-------+-----+-------+-----+-------+
| 2 | A2 | false | 5 | B5 | 3 | C3 |
+----+------+-------+-----+-------+-----+-------+
*/
I try to make something like that:
var result = badClass.GroupBy(x => x.id).Select(x => x. ???? ) ???? =(
but don't know how to make it right.
UPDATE:
var result = badClass
.GroupBy(x => x.id)
.Select(x => new A {
id = x.Key,
name = x.First().name,
flag = x.First().flag,
bList = x.ToList()
});
here is error in bList = x.ToList(); I need to change names bId to id e.t.c.
ANSWER:
var result = badClass
.GroupBy(x => x.id)
.Select(x => new A
{
id = x.Key,
name = x.First().name,
flag = x.First().flag,
bList = badClass.Where(y => y.id == x.Key).Select(y =>
new B { id = y.bId, name = y.bName, c = new C { id = y.cId, name = y.cName } }).ToList()
});
Just add a .Select and ToList which will has all the items you need
var result = badClass.GroupBy(x => x.id).Select(x => x.ToList());
var result = badClass
.GroupBy(x => x.id)
.Select(x => new A
{
id = x.Key,
name = x.First().name,
flag = x.First().flag,
bList = badClass.Where(y => y.id == x.Key).Select(y =>
new B { id = y.bId, name = y.bName, c = new C { id = y.cId, name = y.cName } }).ToList()
});
This should get you what you want
But to be honest I would prefer a foreach loop + Dictionary for readability.
Related
I'm trying to merge two lists and I thought I had a solution but if there are two PackItems with the same length the results are not as expected.
Expectations/requirements.
Both lists contain the same total number of pieces for each length.
EDIT: Added code to clarify the input requirements.
The same length can be used in multiple PacksItems.
The same lengths can be produced out of multiple CoilNums.
The goal is to contain a list the contains a unique entry for each PackItem.ID/CoilNum.
Requirement for the output is that the total number of pieces for each length matched the input lists.
Here is the code I have so far.
public class PackItem
{
public int ID { get; set; }
public int Quantity { get; set; }
public string Length { get; set; }
}
public class ProductionInfo
{
public ProductionInfo AddID(PackItem item)
{
LineID = item.ID;
Quantity = Math.Min(Quantity, item.Quantity);
return this;
}
public int LineID { get; set; }
public string CoilNum { get; set; }
public int Quantity { get; set; }
public string Length { get; set; }
}
private void DoTest()
{
var packItems = new List<PackItem>()
{
new PackItem() {ID = 4, Quantity = 5, Length = "10"},
new PackItem() {ID = 5, Quantity = 2, Length = "4"},
new PackItem() {ID = 6, Quantity = 1, Length = "4"}
};
var productionInfoList = new List<ProductionInfo>()
{
new ProductionInfo() { CoilNum = "A", Quantity = 4, Length = "10"},
new ProductionInfo() { CoilNum = "B", Quantity = 1, Length = "10"},
new ProductionInfo() { CoilNum = "B", Quantity = 2, Length = "4"},
new ProductionInfo() { CoilNum = "A", Quantity = 1, Length = "4"},
};
//assert that both lists meet input requirements
var result1 = "";
var sum1 = packItems.GroupBy(i => i.Length);
foreach (var group in sum1) result1 += $"{group.Sum(i=>i.Quantity)} | {group.Key}\n";
var input2 = "";
var result2 = "";
var sum2 = productionInfoList.GroupBy(i => i.Length);
foreach (var group in sum2) result2 += $"{group.Sum(i => i.Quantity)} | {group.Key}\n";
Console.WriteLine("packItems: \nSum(Quantity) | Length");
Console.WriteLine(result1);
Console.WriteLine("productionInfoList: \nSum(Quantity) | Length");
Console.WriteLine(result2);
if (result1 == result2)
{
Console.WriteLine("Both Lists have the same quantity of each length");
}
else
{
Console.WriteLine("Error: Both Lists do not have the same quantity of each length");
return;
}
var merged = productionInfoList.SelectMany(x => packItems, (x, y) => new { x, y })
.Where(i => i.x.Length == i.y.Length)
.Select(i => i.x.AddID(i.y));
Console.WriteLine("ID | Coil | Qty | Length");
foreach (var item in merged)
{
Console.WriteLine($"{item.LineID} | {item.CoilNum} | {item.Quantity} | {item.Length}");
}
}
//expected output
ID | Coil | Qty | Length
4 | A | 4 | 10
4 | B | 1 | 10
5 | B | 2 | 4
6 | A | 1 | 4
//actual output
ID | Coil | Qty | Length
4 | A | 4 | 10
4 | B | 1 | 10
5 | B | 2 | 4
6 | B | 1 | 4
5 | A | 1 | 4
6 | A | 1 | 4
I'm stuck at this point and they only way I can think of is splitting each of these lists into individual items of one each, and then compiling a list by looping through them one by one.
Is there a way this can be done with Linq?
Here is a method that produces the correct output. Is there an easier way to do this? Can this be done with Linq only?
private void DoTest()
{
var packItems = new List<PackItem>()
{
new PackItem() {ID = 4, Quantity = 5, Length = "10"},
new PackItem() {ID = 5, Quantity = 2, Length = "4"},
new PackItem() {ID = 6, Quantity = 1, Length = "4"}
};
var productionInfoList = new List<ProductionInfo>()
{
new ProductionInfo() { CoilNum = "A", Quantity = 4, Length = "10"},
new ProductionInfo() { CoilNum = "B", Quantity = 1, Length = "10"},
new ProductionInfo() { CoilNum = "B", Quantity = 2, Length = "4"},
new ProductionInfo() { CoilNum = "A", Quantity = 1, Length = "4"},
};
//first create a list with one item for each pieces
var individualProduction = new List<ProductionInfo>();
foreach (var item in productionInfoList)
{
for (int i = 0; i < item.Quantity; i++)
{
individualProduction.Add(new ProductionInfo()
{
Quantity = 1,
Length = item.Length,
CoilNum = item.CoilNum
});
}
}
//next loop through and assign all the pack line ids
foreach (var item in individualProduction)
{
var packItem = packItems.FirstOrDefault(i => i.Quantity > 0 && i.Length == item.Length);
if (packItem != null)
{
packItem.Quantity -= 1;
item.LineID = packItem.ID;
}
else
{
item.Quantity = 0;
}
}
//now group them back into a merged list
var grouped = individualProduction.GroupBy(i => (i.CoilNum, i.LineID, i.Length));
//output the merged list
var merged1 = grouped.Select(g => new ProductionInfo()
{
LineID = g.Key.LineID,
CoilNum = g.Key.CoilNum,
Length = g.Key.Length,
Quantity = g.Count()
});
}
Quite unclear ...
This one is closed of the desired result but doesn't take into consideration any quantity so that the fist PackItem is always choosed. If decreasing the pItem.Quantity this would select the next available pItem.ID where Quantity > 0. But this will require more code :)
var results = productionInfoList.Select(pInfo =>
{
var pItem = packItems.First(z => z.Length == pInfo.Length);
return new { pItem.ID, pInfo.CoilNum, pInfo.Quantity, pInfo.Length };
}).ToList();
When you have a goal of : The goal is to contain a list the contains a unique entry for each PackItem.ID/CoilNum. your bottom answer is correct, since it has a unique id coilNum pair. What you are looking for is a different uniquenes.
var l = packItems.Join(productionInfoList, x => x.Length, y => y.Length, (x, y) => { y.AddID(x); return y; }).GroupBy(x => new { x.CoilNum, x.Length }).Select(x => x.First());
It is unclear on the exact rules of the case, but here I am using Length as a unique key to perform a join operation (Would recommend to have a different unique key for join operations).
I am retrieving records from a db and creating the following object:
public class RemittanceBatchProcessingModel
{
public string FileId { get; set; }
public string SourceFileName { get; set; }
public string BatchCode { get; set; }
public string BatchType { get; set; }
public decimal PaymentAmount { get; set; }
public string BillingSystemCode { get; set; }
}
Example objects created after db read:
FileId | SourceFileName | BatchCode | BatchType | PaymentAmt |BillingCode
1 | test.file1.txt | 100 | S | 1000.00 | Exc
1 | test.file1.txt | 100 | S | 2000.00 | Exc
1 | test.file1.txt | 200 | N | 500.00 | Adc
2 | test.file2.txt | 300 | S | 1200.00 | Exc
2 | test.file2.txt | 300 | S | 1500.00 | Exc
I want to create an object that has a collection of the unique files which has a collection of each summarized batch within a file. For example,
Collection of Unique Files:
FileId | SourceFileName | BatchCode | BatchType | BatchTotal |RecordCount
1 | test.file1.txt | 100 | S | 3000.00 | 2
1 | test.file1.txt | 200 | N | 500.00 | 1
2 | test.file2.txt | 100 | S | 1700.00 | 2
I am able to create my collection of batches with no issue the problem I'm having is figuring out how to create the collection of unique files with the correct batches within them. I'm attempting this using the following:
private static RemittanceCenterFilesSummaryListModel SummarizeFiles(RemittanceCenterSummaryListModel remittanceCenterSummaryListModel)
{
var summarizedBatches = SummarizeBatches(remittanceCenterSummaryListModel);
var fileResult = remittanceCenterSummaryListModel.RemittanceBatchSummaryRecord.GroupBy(x => new { x.FileId, x.SourceFileName })
.Select(x => new RemitanceCenterFileSummarizedModel()
{
FileId = x.Key.FileId,
SourceFileName = x.Key.SourceFileName,
ScannedBatchCount = x.Count(y => y.BatchType == "S"),
ScannedBatchAmount = x.Where(y => y.BatchType == "S").Sum(y => y.PaymentAmount),
NonScannedBatchCount = x.Count(y => y.BatchType != "S"),
NonScannedBatchAmount = x.Where(y => y.BatchType != "S").Sum(y => y.PaymentAmount),
});
var summaryListModel = CreateSummaryFilesListModel(fileResult);
summaryListModel.Batches = summarizedBatches.RemittanceBatchSummary;
return summaryListModel;
}
private static RemittanceCenterFilesSummaryListModel CreateSummaryFilesListModel(IEnumerable<RemitanceCenterFileSummarizedModel> summaryModels)
{
var summaryModelList = new RemittanceCenterFilesSummaryListModel();
foreach (var summaryFileRec in summaryModels)
{
var summaryModel = new RemitanceCenterFileSummarizedModel
{
FileId = summaryFileRec.FileId.ToString(CultureInfo.InvariantCulture),
SourceFileName = summaryFileRec.SourceFileName.ToString(CultureInfo.InvariantCulture),
ScannedBatchCount = summaryFileRec.ScannedBatchCount,
ScannedBatchAmount = summaryFileRec.ScannedBatchAmount,
NonScannedBatchCount = summaryFileRec.NonScannedBatchCount,
NonScannedBatchAmount = summaryFileRec.NonScannedBatchAmount
};
summaryModelList.RemittanceFilesSummary.Add(summaryModel);
}
return summaryModelList;
}
You can group it on the 4 columns including BatchType and BatchCode as well and pick the Count and sum the Amount like :
var fileResult = remittanceCenterSummaryListModel.RemittanceBatchSummaryRecord
.GroupBy(x => new
{
x.FileId,
x.SourceFileName,
x.BatchType,
x.BatchCode
})
.Select(x => new
{
FileId = x.Key.FileId,
SourceFileName = x.Key.SourceFileName,
BatchType = x.Key.BatchType,
BatchCode = x.Key.BatchCode,
BatchTotal= x.Sum(y=>y.PaymentAmt),
RecordCount = x.Count()
});
I guess you need to GroupBy FileId & BatchType instead of FileName:-
var fileResult = remittanceCenterSummaryListModel.RemittanceBatchSummaryRecord
.GroupBy(x => new { x.FileId, x.BatchType })
.Select(x =>
{
var firstObj = x.FirstOrDefault();
return new RemitanceCenterFileSummarizedModel()
{
FileId = x.Key.FileId,
SourceFileName = firstObj.SourceFileName,
BatchCode = firstObj.BatchCode,
BatchType = x.Key.BatchType,
BatchTotal = x.Sum(z => z.PaymentAmt),
RecordCount = x.Count()
};
});
Considering FileId maps to SourceFileName & BatchCode maps to BatchType you can simply store the first set in a variable like I did in firstObj to get the relevant values which are not grouped. Please check for nulls before accessing relevant properties as it may cause NRE if no set is found.
For pure linq non fluent
var files = new[] {
new { FileId = 1, SourceFileName = "test.file1.txt" , BatchCode = 100 , BatchType = "S", PaymentAmt = 1000.00 , BillingCode = "Exc" },
new { FileId = 1, SourceFileName = "test.file1.txt" , BatchCode = 100 , BatchType = "S", PaymentAmt = 2000.00 , BillingCode = "Exc" },
new { FileId = 1, SourceFileName = "test.file1.txt" , BatchCode = 200 , BatchType = "N", PaymentAmt = 500.00 , BillingCode = "Adc" },
new { FileId = 1, SourceFileName = "test.file2.txt " , BatchCode = 300 , BatchType = "S", PaymentAmt = 1200.00 , BillingCode = "Exc" },
new { FileId = 1, SourceFileName = "test.file2.txt " , BatchCode = 300 , BatchType = "S", PaymentAmt = 1500.00 , BillingCode = "Exc" }
};
var result = from file in files
group file by new { file.FileId, file.BatchCode } into fileBachGroups
select new
{
FileId = 1,
SourceFileName = fileBachGroups.First().SourceFileName,
BatchCode = fileBachGroups.Key.BatchCode,
BatchType = fileBachGroups.First().BatchType,
BatchTotal = fileBachGroups.Sum(f => f.PaymentAmt),
RecordCount = fileBachGroups.Count()
};
Console.WriteLine("FileId | SourceFileName | BatchCode | BatchType | BatchTotal |RecordCount");
foreach (var item in result)
{
Console.WriteLine("{0} | {1} | {2} | {3} | {4} | {5}",item.FileId,item.SourceFileName, item.BatchCode, item.BatchType, item.BatchTotal, item.RecordCount);
}
I have three tables. I would like to used C# linq to turn into one table.
For example:
Schedule:
+------+--------+--------+
| Name | DateId | TaskId |
+------+--------+--------+
| John | 2 | 32 |
| John | 3 | 31 |
| Mary | 1 | 33 |
| Mary | 2 | 31 |
| Tom | 1 | 34 |
| Tom | 2 | 31 |
| Tom | 3 | 33 |
+------+--------+--------+
Date:
+----+------------+
| Id | Date |
+----+------------+
| 1 | Monday |
| 2 | Tuesday |
| 3 | Wednesday |
| 4 | Thursday |
| 5 | Friday |
+----+------------+
Task:
+----+----------+
| Id | Task |
+----+----------+
| 31 | School |
| 32 | Homework |
| 33 | Break |
| 34 | Teaching |
+----+----------+
I would like to have a table like this:
+--------+----------+----------+-----------+----------+
| Person | Monday | Tuesday | Wednesday | Thursday |
+--------+----------+----------+-----------+----------+
| John | | Homework | School | |
| Mary | Break | School | | |
| Tom | Teaching | School | Break | |
+--------+----------+----------+-----------+----------+
I could not think of any good way doing this.
Any suggestion would be helpful
Thanks
Starting from this set of data:
var schedules = new[] { new { Name = "John", DateId = 2, TaskId = 32},
new { Name = "John", DateId = 3, TaskId = 31},
new { Name = "Mary", DateId = 1, TaskId = 33},
new { Name = "Mary", DateId = 2, TaskId = 31},
new { Name = "Tom", DateId = 1, TaskId = 34},
new { Name = "Tom", DateId = 2, TaskId = 31},
new { Name = "Tom", DateId = 3, TaskId = 33}
};
var dates = new[] { new { DateId = 1, Desc = "Monday"},
new { DateId = 2, Desc = "Tuesday"},
new { DateId = 3, Desc = "Wednesday"},
new { DateId = 4, Desc = "Thursday"},
new { DateId = 5, Desc = "Friday"}
};
var tasks = new[] { new { TaskId = 31, Desc = "School"},
new { TaskId = 32, Desc = "Homework"},
new { TaskId = 33, Desc = "Break"},
new { TaskId = 34, Desc = "Teaching"}
};
You can do as follows:
var result = schedules
// First you join the three tables
.Join(dates, s => s.DateId, d => d.DateId, (s, d) => new {s, d})
.Join(tasks, s => s.s.TaskId, t => t.TaskId, (sd, t ) => new { Person = sd.s, Date = sd.d, Task = t })
// Then you Group by the person name
.GroupBy(j => j.Person.Name)
// Finally you compose the final object extracting from the list of task the correct task for the current day
.Select(group => new
{
Person = group.Key,
Monday = group.Where(g => g.Date.DateId == 1).Select(g => g.Task.Desc).FirstOrDefault(),
Tuesday = group.Where(g => g.Date.DateId == 2).Select(g => g.Task.Desc).FirstOrDefault(),
Wednesday = group.Where(g => g.Date.DateId == 3).Select(g => g.Task.Desc).FirstOrDefault(),
Thursday = group.Where(g => g.Date.DateId == 4).Select(g => g.Task.Desc).FirstOrDefault(),
Friday = group.Where(g => g.Date.DateId == 5).Select(g => g.Task.Desc).FirstOrDefault()
})
.ToList();
If you want to select only some days, you can return an object containing a dictionary instead of an object with a property per day.
The dictionary will contain key-value pairs with the key representing the day and the value representing the task.
See the following code:
var filter = new[] {2, 3};
var filteredResult = schedules
.Join(dates, s => s.DateId, d => d.DateId, (s, d) => new{ s, d})
.Join(tasks, s => s.s.TaskId, t => t.TaskId, (sd, t) => new { Person = sd.s, Date = sd.d, Task = t })
.Where(x => filter.Contains(x.Date.DateId))
.GroupBy(x => x.Person.Name)
.Select(group => new
{
Person = group.Key,
TasksByDay = group.ToDictionary(o => o.Date.Desc, o => o.Task.Desc)
})
.ToList();
foreach (var item in filteredResult)
{
System.Console.WriteLine(item.Person);
foreach (var keyvaluepair in item.TasksByDay)
{
System.Console.WriteLine(keyvaluepair.Key + " - " + keyvaluepair.Value);
}
System.Console.WriteLine("---");
}
It is called "transpose".
var persons = new[] { new { name="John", dateId=2,taskId=32},
new { name="John", dateId=3,taskId=31},
new { name="Mary", dateId=1,taskId=33},
new { name="Mary", dateId=2,taskId=31},
new { name="Tom", dateId=1,taskId=34},
new { name="Tom", dateId=2,taskId=31},
new { name="Tom", dateId=3,taskId=33}
};
var dates = new[] { new { dateId=1, desc="Monday"},
new { dateId=2, desc="Tuesday"},
new { dateId=3, desc="Wednesday"},
new { dateId=4, desc="Thursday"},
new { dateId=5, desc="Friday"}
};
var tasks = new[] { new { taskId=31, desc="School"},
new { taskId=32, desc="Homework"},
new { taskId=33, desc="Break"},
new { taskId=34, desc="Teaching"}
};
var qry = from p in (from p in persons
join d in dates on p.dateId equals d.dateId
join t in tasks on (int)p.taskId equals (int)t.taskId
select new { name = p.name, monday = d.dateId == 1 ? t.desc : "", tuesday = d.dateId == 2 ? t.desc : "", wednesday = d.dateId == 3 ? t.desc : "", thursday = d.dateId == 4 ? t.desc : "", friday = d.dateId == 5 ? t.desc : "" })
group p by p.name into q
select new { q.Key, monday=q.Max(a => a.monday),tuesday=q.Max(a => a.tuesday), wednesday = q.Max(a=>a.wednesday), thursday = q.Max(a => a.thursday), friday=q.Max(a => a.friday)};
foreach ( var a in qry.ToList())
{
Console.WriteLine(String.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}",a.Key, a.monday, a.tuesday, a.wednesday, a.thursday, a.friday));
}
I have the following tables:
Readings:
+----+---------------------+-------+----------+
| Id | TimestampLocal | Value | Meter_Id |
+----+---------------------+-------+----------+
| 1 | 2014-08-22 18:05:03 | 50.5 | 1 |
| 2 | 2013-08-12 14:02:09 | 30.2 | 1 |
+----+---------------------+-------+----------+
Meters:
+----+--------+
| Id | Number |
+----+--------+
| 1 | 32223 |
+----+--------+
I need to select 2 readings for each meter, the reading with max DateTime and the reading with min DateTime, in addition to the difference between values of the two readings, something like this:
+----------+------------+----------------+------------+----------------+------------+
| Meter_Id | MaxReading | MaxReadingTime | MinReading | MinReadingTime | Difference |
+----------+------------+----------------+------------+----------------+------------+
I need a single query to achieve this for all meters within a date range in Entity Framework
i was able to get this far (get max and min readings):
SELECT
tt.*
FROM Readings tt
INNER JOIN
(
SELECT
Meter_Id,
MAX(TimeStampLocal) AS MaxDateTime,
MIN(TimeStampLocal) AS MinDateTime
FROM Readings
where TimeStampLocal > '2014-12-08'
GROUP BY Meter_Id
) AS groupedtt
ON (tt.Meter_Id = groupedtt.Meter_Id) AND
(tt.TimeStampLocal = groupedtt.MaxDateTime or tt.TimeStampLocal = groupedtt.MinDateTime)
order by Meter_Id;
Using this mockup of your actual schema and data:
class Reading
{
public int Id { get; set; }
public DateTime TimestampLocal { get; set; }
public double Value { get; set; }
public int Meter_Id { get; set; }
}
List<Reading> Readings = new List<Reading>()
{
new Reading { Id = 1, TimestampLocal = new DateTime(2014, 8, 22), Value = 50.5, Meter_Id = 1 },
new Reading { Id = 2, TimestampLocal = new DateTime(2013, 8, 12), Value = 30.2, Meter_Id = 1 },
new Reading { Id = 3, TimestampLocal = new DateTime(2013, 9, 12), Value = 35.2, Meter_Id = 1 }
};
using this linq query:
var q = from r in Readings
group r by r.Meter_Id into rGroup
select new
{
Meter_Id = rGroup.Key,
MaxReading = rGroup.OrderByDescending(x => x.TimestampLocal).First().Id,
MaxReadingTime = rGroup.OrderByDescending(x => x.TimestampLocal).First().TimestampLocal,
MinReading = rGroup.OrderBy(x => x.TimestampLocal).First().Id,
MinReadingTime = rGroup.OrderBy(x => x.TimestampLocal).First().TimestampLocal,
Difference = rGroup.OrderByDescending(x => x.TimestampLocal).First().Value -
rGroup.OrderBy(x => x.TimestampLocal).First().Value
};
produces this output:
[0] = { Meter_Id = 1, MaxReading = 1, MaxReadingTime = {22/8/2014 12:00:00 πμ},
MinReading = 2, MinReadingTime = {12/8/2013 12:00:00 πμ}, Difference = 20.3 }
which should be close to expected result.
EDIT:
You can considerably simplify the above linq query by making use of the let clause:
var q = from r in Readings
group r by r.Meter_Id into rGroup
let MaxReading = rGroup.OrderByDescending(x => x.TimestampLocal).First()
let MinReading = rGroup.OrderBy(x => x.TimestampLocal).First()
select new
{
Meter_Id = rGroup.Key,
MaxReading = MaxReading.Id,
MaxReadingTime = MaxReading.TimestampLocal,
MinReading = MinReading.Id,
MinReadingTime = MinReading.TimestampLocal,
Difference = MaxReading.Value - MinReading.Value
};
Probably not the most efficient, I admit, but that's the quickest I could come up with something without counter-verifying it myself.
SELECT m.Id AS Meter_Id, MaxReading, MaxReadingTime, MinReading, MinReadingTime, (MaxReading - MinReading) AS Difference
FROM Meters m
CROSS APPLY (SELECT MIN(Value) MinReading, TimestampLocal AS MinReadingTime FROM Readings WHERE Meter_Id = m.Id) min
CROSS APPLY (SELECT MAX(Value) MaxReading, TimestampLocal AS MaxReadingTime FROM Readings WHERE Meter_Id = m.Id) max
edit: formatting.
I have a class that looks like so:
{
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
public int DepId{ get; set; }
}
}
Data that my list folds looks like so:
ID | Name | CategoryId | DepId
---------------------------------------
1 | Post | 1 | 1
2 | Post | 1 | 2
3 | Printer | 2 | 1
4 | Printer | 2 | 3
5 | Post | 3 | 3
6 | Printer | 2 | 1
This data holds Access data to some categories.
What I would like to get is common categories for 2 (or more) departaments
If user selects that he want categories for department with id=1 then he should get elements with id 1 and 3, but if he wants categories for department 1 and 3 he should get elements 4 and 6.
For DepId IN(1,3) I would like to get this result:
Name | CategoryId
----------------------
Printer | 2
Something like JOIN in SQL.
I was able to code it in sql:
SELECT * FROM(
SELECT
C.Cat_Id AS Id,
MAX(C.Name) AS Name,
FROM
Categories_Access AS CA (NOLOCK)
JOIN dbo.Categories AS C (NOLOCK) ON C.Cat_Id = CA.Cat_Id
WHERE
CA.DepId IN (1,3)
GROUP BY C.Cat_Id
HAVING COUNT(*)=2
) A ORDER BY A.Name
Now I would like to do same thing in C#.
EDIT
This is my attempt:
var cat = new List<Category>();
cat.Add(new Category {Id = 1, CategoryId = 1, Name = "Post", DepId = 1});
cat.Add(new Category {Id = 2, CategoryId = 1, Name = "Post", DepId = 2});
cat.Add(new Category {Id = 3, CategoryId = 2, Name = "Printer", DepId = 1});
cat.Add(new Category {Id = 4, CategoryId = 2, Name = "Printer", DepId = 3});
cat.Add(new Category {Id = 5, CategoryId = 3, Name = "Another", DepId = 3});
cat.Add(new Category {Id = 6, CategoryId = 2, Name = "Printer", DepId = 1});
cat.Add(new Category {Id = 7, CategoryId = 4, Name = "Else", DepId = 1});
var ids = new List<int> {1, 2};
var Query = from p in cat.Where(i => ids.Contains(i.DepId)).GroupBy(p => p.CategoryId)
select new
{
count = p.Count(),
p.First().Name,
p.First().CategoryId
};
What I need to do is just to select items that have count=ids.Count.
My finale version (based on #roughnex answer):
private static IEnumerable<Cat> Filter(IEnumerable<Category> items, List<int> ids)
{
return items.Where(d => ids.Contains(d.DepId))
.GroupBy(g => new { g.CategoryId, g.Name })
.Where(g => g.Count() == ids.Count)
.Select(g => new Cat { Id = g.Key.CategoryId, Name = g.Key.Name });
}
In C# (LINQ) to select common elements you ll use
List<int> Depts = new List<int>() {1, 3};
var result = Categories.Where(d => Depts.Contains(d.DeptId))
.GroupBy(g => new {g.CatId, g.Name})
.Where(g => g.Count() >= 2)
.Select(g => new {g.Key.CatId, g.Key.Name});
So based on what you've said, you've already parsed the data from a SQL proc into a List<Category>. If that is the case, the following snippet should guide you:
var items = new List<Category>();
var deptIds = new List<int>() { 1, 3 };
var query = items.Where(item => deptIds.Contains(item.DepId))
.Select(category => category);
When you want to do an IN in LINQ, you have to invert it and use Contains. Whereas in SQL it's ColumnName IN (List), in LINQ it's List.Contains(ColumnName). Hope that helps.