translate row_number over partition to C# linq - c#

How to translate the following SQL query into C# Linq:
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY StuffConditionId, StuffId ORDER BY StuffDayOfYear) AS RowNumber
FROM Stuff) rop
WHERE rop.RowNumber = 1;
Here is a partial dataset:
StuffId,StuffValue,StuffConditionId,StuffDayOfYear
2,9340,NULL,1
2,9340,NULL,2
2,9340,NULL,3
11,78,NULL,267
11,78,NULL,268
11,78,NULL,269
43,0,3,130
43,0,3,131
43,0,3,132
43,0,2,133
45,0,2,134
45,0,2,135
45,0,2,148
55,0,2,309
55,0,2,332
55,0,3,333
Answer Summary: The answer is to first build a in-memory list of stuffs, i.e. local list variable, then apply the LINQ query as shown in answer below.

Without some actual data I couldn't test this. But here's how this can be done, assuming stuff is your collection (aka table):
var firstInCollection = Stuff
.OrderBy(x => x.StuffDayOfYear)
.ToList() // Load in memory, then do groupby and select first due to EF Core
.GroupBy(x => new { condition = x.StuffConditionId, stuff = x.StuffId })
.Select(g => g.First());
Ok, I've tried this on a data table with a list of countries. Here are my results:
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY SUBSTRING(CountryCode, 1, 1) ORDER BY CountryCode) AS RowNumber
FROM Lookup.Country) rop
WHERE rop.RowNumber = 1;
and got the following results in SQL
Then I used the following Linq statement. This is using Linq2SQL in LinqPad connected to my Microsoft SQL Database:
Countries
.OrderBy(c => c.CountryName)
.GroupBy(c => c.CountryName[0])
.Select(g => g.First())
And got the following result:
Which correlates with the SQL results.
Here's the example with your example data
void Main()
{
var stuffs = new []
{
new Stuff { StuffId = 2, StuffValue = 9340, StuffConditionId = null, StuffDayOfYear = 1 },
new Stuff { StuffId = 2, StuffValue = 9340, StuffConditionId = null, StuffDayOfYear = 2 },
new Stuff { StuffId = 2, StuffValue = 9340, StuffConditionId = null, StuffDayOfYear = 3 },
new Stuff { StuffId = 11, StuffValue = 78, StuffConditionId = null, StuffDayOfYear = 267 },
new Stuff { StuffId = 11, StuffValue = 78, StuffConditionId = null, StuffDayOfYear = 268 },
new Stuff { StuffId = 11, StuffValue = 78, StuffConditionId = null, StuffDayOfYear = 269 },
new Stuff { StuffId = 43, StuffValue = 0, StuffConditionId = 3, StuffDayOfYear = 130 },
new Stuff { StuffId = 43, StuffValue = 0, StuffConditionId = 3, StuffDayOfYear = 131 },
new Stuff { StuffId = 43, StuffValue = 0, StuffConditionId = 3, StuffDayOfYear = 132 },
new Stuff { StuffId = 43, StuffValue = 0, StuffConditionId = 2, StuffDayOfYear = 133 },
new Stuff { StuffId = 45, StuffValue = 0, StuffConditionId = 2, StuffDayOfYear = 134 },
new Stuff { StuffId = 45, StuffValue = 0, StuffConditionId = 2, StuffDayOfYear = 135 },
new Stuff { StuffId = 45, StuffValue = 0, StuffConditionId = 2, StuffDayOfYear = 148 },
new Stuff { StuffId = 55, StuffValue = 0, StuffConditionId = 2, StuffDayOfYear = 309 },
new Stuff { StuffId = 55, StuffValue = 0, StuffConditionId = 2, StuffDayOfYear = 332 },
new Stuff { StuffId = 55, StuffValue = 0, StuffConditionId = 3, StuffDayOfYear = 333 }
};
var firstInCollection = stuffs
.OrderBy(x => x.StuffDayOfYear)
.GroupBy(x => new { condition = x.StuffConditionId, stuff = x.StuffId })
.Select(g => g.First())
.Dump();
}
class Stuff
{
public int StuffId { get; set; }
public int StuffValue { get; set; }
public int? StuffConditionId { get; set; }
public int StuffDayOfYear { get; set; }
}
This results in the following:

Related

LINQ to Object - How to implement dynamic SELECT projection of sub elements

I have several classes of business logic:
public class Client {
public string Code { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string Account { get; set; } = string.Empty;
public Total Total { get; set; } = new Total();
public List<Month> Months { get; set; } = new List<Month>();
}
public class Month {
public int Number { get; set; } = 0;
public string Name { get; set; } = string.Empty;
public DateTime Start { get; set; } = new DateTime();
public DateTime End { get; set; } = new DateTime();
public Total Summary { get; set; } = new Total();
}
public class Total {
public int Count { get; set; } = 0;
public decimal Sum { get; set; } = 0.0m;
}
which are instanced as follows:
List<Client> clients = new List<Client>() {
new Client {
Code = "7002.70020604",
Status = "Active",
Account = "7002.915940702810005800001093",
Total = new Total {
Count = 9,
Sum = 172536.45m
},
Months = new List<Month>() {
new Month {
Number = 0,
Name = "January",
Start = new DateTime(2021, 1, 1, 0, 0, 0),
End = new DateTime(2021, 1, 31, 23, 59, 59),
Summary = new Total {
Count = 6,
Sum = 17494.50m
}
},
new Month {
Number = 1,
Name = "February",
Start = new DateTime(2021, 2, 1, 0, 0, 0),
End = new DateTime(2021, 2, 28, 23, 59, 59),
Summary = new Total {
Count = 3,
Sum = 155041.95m
}
},
new Month {
Number = 2,
Name = "March",
Start = new DateTime(2021, 3, 1, 0, 0, 0),
End = new DateTime(2021, 3, 31, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
}
}
},
new Client {
Code = "7002.70020604",
Status = "Active",
Account = "7002.800540702810205800001093",
Total = new Total {
Count = 4,
Sum = 16711.21m
},
Months = new List<Month>() {
new Month {
Number = 0,
Name = "January",
Start = new DateTime(2021, 1, 1, 0, 0, 0),
End = new DateTime(2021, 1, 31, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
},
new Month {
Number = 1,
Name = "February",
Start = new DateTime(2021, 2, 1, 0, 0, 0),
End = new DateTime(2021, 2, 28, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
},
new Month {
Number = 2,
Name = "March",
Start = new DateTime(2021, 3, 1, 0, 0, 0),
End = new DateTime(2021, 3, 31, 23, 59, 59),
Summary = new Total {
Count = 4,
Sum = 16711.21m
}
}
}
}
};
I'm trying to arrange aggregate data of a view like this:
+---------------+--------+------------------+-------------------+------------------+-------------------+
| Code | Status | January | February | March | Total |
| | +-------+----------+-------+-----------+-------+----------+-------+-----------+
| | | Count | Sum | Count | Sum | Count | Sum | Count | Sum |
+---------------+--------+-------+----------+-------+-----------+-------+----------+-------+-----------+
| 7002.70020604 | Active | 6 | 17494.50 | 3 | 155041.95 | 4 | 16711.21 | 13 | 189247.66 |
+---------------+--------+-------+----------+-------+-----------+-------+----------+-------+-----------+
using projection like this:
clients
.GroupBy(x => x.Code)
.Select(y => new {
Code = y.First().Code,
Status = y.First().Status,
Account = y.First().Account,
Total = new {
Count = y.Sum(z => z.Total.Count),
Sum = y.Sum(z => z.Total.Sum)
},
Months = new {
/*
?
*/
}
});
But I can't project the data by month. Assuming the date range (months) can be more than just this example. Please help!
Full interactive code listing at dotnetfiddle
You can use SelectMany to get months out of y and then group by month similarly as you group by code:
//...
Months = y
.SelectMany(client => client.Months)
.GroupBy(month => month.Name, (_, months) => new {
Number = months.First().Number,
Name = months.First().Name,
Start = months.First().Start,
End = months.First().End,
Summary = new {
Count = months.Sum(z => z.Summary.Count),
Sum = months.Sum(z => z.Summary.Sum)
}
}).ToList()
//...
That being said I don't suggest to use y.First() or months.First() more than once in each function because it makes an enumeration each time it is used. The following should in general have better performance:
(_, months) => {
var month = months.First();
return new {
Number = month.Number,
Name = month.Name,
Start = month.Start,
End = month.End,
Summary = new {
Count = months.Sum(z => z.Summary.Count),
Sum = months.Sum(z => z.Summary.Sum)
}
}
}
which is also not ideal because we're still making 3 enumerations here (1 enumeration in .First() and 1 enumeration for every .Sum(...)).
Even better approach would be to use Aggregate function which will do only a single enumeration:
(_, months) => months
.Aggregate((res, nextVal) => new Month {
Number = nextVal.Number,
Name = nextVal.Name,
Start = nextVal.Start,
End = nextVal.End,
Summary = new Total {
Count = res.Summary.Count + nextVal.Summary.Count,
Sum = res.Summary.Sum + nextVal.Summary.Sum
}
})
This LINQ query should prepare data for visualization:
clients
.GroupBy(x => new {x.Code, x.Status})
.Select(g => new
{
Code = g.Key
MonthsSummary = g.SelectMany(x => x.Months)
.OrderBy(x => x.Start)
.GroupBy(x => new {x.Start, x.Name})
.Select(gm => new
{
gm.Key.Name,
Count = gm.Sum(x => x.Summary.Count),
Sum = gm.Sum(x => x.Summary.Sum),
})
.ToList()
});

Asserting in MSTEST that a collection OrderBy worked properly

Good morning. I am writing a unit test to validate that my API handler is sorting the collection properly. As you can see, it's mocked data and I intentionally created the test data out of order to test the OrderBy functionality. Once the handler returns the DTO collection, I want to validate that the "act" collection is in the same order as my "expectedDTO" collection. Here's the code:
[TestMethod]
public async Task GetByDay_ReturnsDtoList_WhenFound()
{
var testData = TestMethodData();
var expectedCalendarDto = ExpectedDto();
var testCalendar = testData;
var query = new GetCalendarByDaysQuery();
_mockCalendarRepo.Setup(m => m.GetItemsAsync(It.IsAny<ISpecification<CalendarDay>>(), null, null))
.ReturnsAsync(testCalendar);
var sut = new GetCalendarByDaysHandler(_mockCalendarRepo.Object, _mapper);
var act = await sut.HandleAsync(query);
Assert.IsNotNull(act);
Assert.IsInstanceOfType(act, typeof(IEnumerable<CalendarDto>));
Assert.AreEqual(expectedCalendarDto, act);
}
private GetItemsResult<IEnumerable<CalendarDay>> TestMethodData()
{
var testData = new GetItemsResult<IEnumerable<CalendarDay>>();
testData.ContinuationToken = null;
testData.Results = new List<CalendarDay>()
{
new CalendarDay { Id = "4-5-4|2021-04-01", FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 2, Period = 4, CalendarDate = "2021-04-01", WeekOfYear = 13, DayOfYear = 61, WeekOfPeriod = 1 },
new CalendarDay { Id = "4-5-4|2021-08-01", FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 3, Period = 8, CalendarDate = "2021-08-01", WeekOfYear = 24, DayOfYear = 121, WeekOfPeriod = 1 },
new CalendarDay { Id = "4-5-4|2021-01-01", FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 1, Period = 1, CalendarDate = "2021-01-01", WeekOfYear = 1, DayOfYear = 1, WeekOfPeriod = 1 }
};
return testData;
}
private IEnumerable<CalendarDto> ExpectedDto()
{
var testDto = new List<CalendarDto>()
{
new CalendarDto { FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 1, Period = 1, CalendarDate = "2021-01-01", WeekOfYear = 1, DayOfYear = 1, WeekOfPeriod = 1 },
new CalendarDto { FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 2, Period = 4, CalendarDate = "2021-04-01", WeekOfYear = 13, DayOfYear = 61, WeekOfPeriod = 1 },
new CalendarDto { FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 3, Period = 8, CalendarDate = "2021-08-01", WeekOfYear = 24, DayOfYear = 121, WeekOfPeriod = 1 }
};
return testDto;
}
Currently, in trying to compare the two collections, the test is failing saying that they aren't equal.
Assert.AreEqual failed. Expected:<System.Collections.Generic.List`1[cscentcalendar.infrastructure.DTOs.CalendarDto]>. Actual:<System.Collections.Generic.List`1[cscentcalendar.infrastructure.DTOs.CalendarDto]>.
Not sure what I'm doing wrong. Any assistance would be greatly appreciated.
I believe I solved the problem by using fluent assertion and adding the following line of code to test the two collections. Let me know if this is incorrect. Thanks again!
expectedCalendarDto.Should().BeEquivalentTo(act);

How to split array to many arrays where id same linq

I have array:
OrderProduct[] OrderProductsOrder = new OrderProduct[] {
new OrderProduct { OrderID = 1, ProductID = 2, OrderCustomerID = 1 },
new OrderProduct { OrderID = 2, ProductID = 1, OrderCustomerID = 1 },
new OrderProduct { OrderID = 1, ProductID = 3, OrderCustomerID = 1 },
new OrderProduct { OrderID = 2, ProductID = 1, OrderCustomerID = 2 },
new OrderProduct { OrderID = 1, ProductID = 2, OrderCustomerID = 3 },
new OrderProduct { OrderID = 2, ProductID = 1, OrderCustomerID = 3 }};
How to split this array to three arrays, order by CustomerID, using linq.
Result should be this three arrays:
OrderProduct[] Customer1Order = new OrderProduct[] {
new OrderProduct { OrderID = 1, ProductID = 2, OrderCustomerID = 1 },
new OrderProduct { OrderID = 2, ProductID = 1, OrderCustomerID = 1 },
new OrderProduct { OrderID = 1, ProductID = 3, OrderCustomerID = 1 }};
OrderProduct[] Customer2Order = new OrderProduct[]
{new OrderProduct { OrderID = 2, ProductID = 1, OrderCustomerID = 2 }};
OrderProduct[] Customer3Order = new OrderProduct[] {
new OrderProduct { OrderID = 1, ProductID = 2, OrderCustomerID = 3 },
new OrderProduct { OrderID = 2, ProductID = 1, OrderCustomerID = 3 }};
Edited, removed the GroupBy() suggestion as it was redundant (courtesy of Innat3)
No reason to use GroupBy() at all, just use Where.
OrderProduct[] Customer1Order = OrderProductsOrder.Where(o => o.OrderCustomerID == 1).ToArray();
OrderProduct[] Customer2Order = OrderProductsOrder.Where(o => o.OrderCustomerID == 2).ToArray();
OrderProduct[] Customer3Order = OrderProductsOrder.Where(o => o.OrderCustomerID == 3).ToArray();
Start by grouping the entries by OrderCustomerID, and constructing an array from each group. After that, add groups to a dictionary:
var byCustId = OrderProductsOrder
.GroupBy(p => p.OrderCustomerID)
.ToDictionary(g => g.Key, g => g.ToArray());
Now you can grab individual arrays with TryGetValue or operator []:
OrderProduct[] customer2Order;
if (byCustId.TryGetValue(2, out customer2Order) {
... // Use customer2Order array
}

SQL Query Order of Operations

It Friday. My brain is fried. This is very simple and I'm ashamed for asking this:
I simply want to query against my Event table (which has a non-null Start Time and nullable End time).However, given my unit test, I keep getting 2 records back (the 2:00 and the 4:00 records, not just the 2:00 as I'd expect)
SELECT
EventId,
TaskId,
MachineId,
LoginId,
EventStartTimeUtc,
EventEndTimeUtc,
OpCode,
UnitId,
PositionId,
WebId,
Comment,
MakereadyCount,
GrossCount,
NetCount,
PerpetualGross,
PerpetualNet,
PerpetualMakeready,
TaskState,
EventTypeId,
IsAutoEvent,
IsTransferred,
LastUpdatedTimeUtc
FROM Event
WHERE MachineId = #MachineId#
AND EventStartTimeUtc >= #StartTimeUtc#
AND (EventEndTimeUtc IS NULL
OR ((EventEndTimeUtc IS NOT NULL) AND EventEndTimeUtc <![CDATA[<=]]> #EndTimeUtc#))
[Test]
public void ShouldSelectEventsInRange()
{
//Arrange
TaskDto testTask = _testRepository.CreateTask(new TaskDto { TaskId = 1234567 }, true);
var machineId = ((ArtemisRepository)_testRepository).CreateMachine(123, "MR40SIM", "0V7", 200, 100, 555555); //Requires a 555555 down-task to exist in database
EventRecordDto result = _testRepository.CreateEvent(new EventRecordDto {TaskId = testTask.TaskId, MachineId = machineId, EventStartTimeUtc = new DateTime(2014, 4, 15, 1, 50, 0), OpCode = "100", MakereadyCount = 1752, GrossCount = 5660, NetCount = 2512, Comment = "Test Event", IsAutoEvent = false, IsTransferred = false});
EventRecordDto result2 = _testRepository.CreateEvent(new EventRecordDto {TaskId = testTask.TaskId, MachineId = machineId, EventStartTimeUtc = new DateTime(2014, 4, 15, 2, 0, 0), OpCode = "100", MakereadyCount = 1752, GrossCount = 5660, NetCount = 2512, Comment = "Test Event", IsAutoEvent = false, IsTransferred = false});
EventRecordDto result3 = _testRepository.CreateEvent(new EventRecordDto {TaskId = testTask.TaskId, MachineId = machineId, EventStartTimeUtc = new DateTime(2014, 4, 15, 4, 0, 0), OpCode = "100", MakereadyCount = 1752, GrossCount = 5660, NetCount = 2512, Comment = "Test Event", IsAutoEvent = false, IsTransferred = false});
//Act
var results = _testRepository.SelectEventsInRange(machineId, new DateTime(2014, 4, 15, 2, 0, 0), new DateTime(2014, 4, 15, 3, 59, 59));
//Assert
Assert.IsTrue(results.Count == 1, "{0} records came, instead of the 1 record expeted!", new object[] { results.Count });
Assert.IsTrue(results.Any(r => r.EventId == result2.EventId), "Expected Event (Id: {0}), Actual Event (ID: {1})", new object[] { result2.EventId, results[0].EventId});
}
The query asks for records where the EventStartTimeUtc is greater than or equal to 2:00, which both those records are, and where the EventEndTimeUtc is null, which they both are.
Did you mean EventStartTimeUtc where you have EventEndTimeUtc in the WHERE clause?

EF LINQ query where meet all list or array

i have these data:
class MyTableItem
{
public long id { get; set; }
public long listId { get; set; }
public long listFieldValue { get; set; }
public long parentId { get; set; }
}
and:
var myData = new MyTableItem[]
{
new MyTableItem { id = 1, listId = 1, listFieldValue = 100, parentId = 1 },
new MyTableItem { id = 2, listId = 2, listFieldValue = 130, parentId = 1 },
new MyTableItem { id = 3, listId = 3, listFieldValue = 170, parentId = 1 },
new MyTableItem { id = 4, listId = 4, listFieldValue = 170, parentId = 1 },
new MyTableItem { id = 5, listId = 1, listFieldValue = 100, parentId = 2 },
new MyTableItem { id = 6, listId = 2, listFieldValue = 130, parentId = 2 },
new MyTableItem { id = 7, listId = 3, listFieldValue = 170, parentId = 2 },
new MyTableItem { id = 8, listId = 4, listFieldValue = 270, parentId = 2 },
...(continue)
};
var myMatchConditions = new int?[][] //id, rangeTypeId(equal, more, less, between), from, to
{
new int?[] { 1, 1, 100, null },
new int?[] { 2, 2, 125, null },
new int?[] { 3, 3, null, 175 },
new int?[] { 4, 4, 130, 180 }
...(can continue more)
};
now i need to know which myData (groupBy parrentId) are matched by my conditions,
let me explain more:
I want to know which parrent Id has listFieldValue where:
1) (listId == 1)&&(listFieldValue == 100)
and
2) (listId == 2)&&(listFieldValue > 125)
and
3) (listId == 3)&&(listFieldValue < 175)
and
4) ((listId == 4)&&(listFieldValue > 130)&&(listFieldValue < 180))
it must return (1)parrentId.
There you go. Explanations are at the bottom:
IEnumurable<MyTableItem> temp = myData ;
for (int i = 0; i < myMatchConditions.GetLength(0); i++)
{
var conditionType = myMatchConditions[i,1];
if (conditionType == 1)
{
temp = temp.Where(_ => _listFieldValue == myMatchConditions[i,2]);
}
else
{
if (conditionType == 2 || conditionType == 4)
{
temp = temp.Where(_ => _listFieldValue > myMatchConditions[i,2]);
}
if (conditionType == 3 || conditionType == 4)
{
temp = temp.Where(_ => _listFieldValue < myMatchConditions[i,3]);
}
}
}
I'm using IEnumurable<MyTableItem> which means it's Linq and not Linq to entities. I chose that because your myData is not an EF table but a simple array.
I go through all the "rows" with a for, you can do that with a foreach, and I add the Where clauses to filter out more and more each time (The actual filtering will happen only when you use that temp list)
I add a condition based on the type in the second cell, and if the type is 4... I add both the 2 and 3 type rules... which makes a 4 type rule

Categories