how to add other fields to group by linq - linq

I have to select and group by because I need to get the average and sum of some items, my problem is first of all the group by is based on month(m_date.Month) so inside the group by I don't have access to year anymore, my second problem is what if I want to have other properties from statisticsDaily class in my query? with this query I only have access to grouped by fields, look at below:
var rslt = await (from d in db.statMonth.Include(f=>f.MasterData).Where(d=>d.m_turbine_id == IPAddress.Parse(id) && d.m_date >= frm)
group d by d.m_date.Month into g
select new statisticsDaily
{
Date = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(g.Key),
Production = g.Sum(s => s.m_energy_prod),
m_wind_speed = g.Average(s => s.m_wind_speed),
Availability = g.Average(s => s.m_availability),
}
).OrderBy(s=>s.Date).ToListAsync();
my statsticDaily class is :
public class statisticsDaily
{
public string Date { get; set; }
public Nullable<float> Production { get; set; }
public Nullable<float> m_wind_speed { get; set; }
public Nullable<float> Availability { get; set; }
public string Comments { get; set; }
public string TurbineId { get; set; }
public string Countries { get; set; }
}

This is exactly how group by works. If you need to access Year too so you need to group by Month and Year together:
var rslt = await (from d in db.statMonth.Include(f=>f.MasterData).Where(d=>d.m_turbine_id == IPAddress.Parse(id) && d.m_date >= frm)
group d by new { d.m_date.Month, d.m_date.Year} into g
select new statisticsDaily
{
Year = g.Key.Year// We can access Year now since we grouped by Year as well
Date = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(g.Key.Month),
Production = g.Sum(s => s.m_energy_prod),
m_wind_speed = g.Average(s => s.m_wind_speed),
Availability = g.Average(s => s.m_availability),
}
).OrderBy(s=>s.Date).ToListAsync();
See: Using group by on multiple columns
On the other, You can access to list of other properties as g.ToList() when you group by items. See: LINQ Group By and select collection

Every StatMonth has a property m_Date. Your GroupBy makes groups of StatMonths that have equal value for m_Date.Month.
So the StatMonth with an m_Date in February 2019 will be in the same Group as all StatMonth with an m_Date in Februari 2020 and Februari 2018.
You wrote:
...so inside the group by I don't have access to year anymore,
It depend on what you want to do with the Year.
Do you want to make groups of StatMonths with m_Date in the same month of the same year? So do you want to make a group of StatMonths of 2019-02, 2019-03, 2019-04, ..., 2020-02, 2020-03, etc?
Or do you want to make a February group of sub groups: the 2019 sub group, the 2020 sub group, etc. And similarly a March group with 2019 sub group, 2020 sub group.
If you look at the generic method syntax of GroupBy, you'll see the following:
var result = db.StatMonths
.Where(...)
.GroupBy(
// parameter KeySelector: I want to make Groups of StatMonths that have the
// same value for this key:
statMonth => ... // this will be the key,
// for example: groups with same Year/Month combination of m_Date
statMonth => new
{
Year = statMonth.m_Date.Year,
Month = statMonth.m_Date.Month,
},
// parameter resultSelector, for every Key, and all StatMonths that have this key
// make one new object:
(yearMonthCombination, statMonthsWithThisYearMonthCombination) => new ...
}
So in parameter resultSelector, you get the key, and all items that have this key. So if you need groups with same year/month combination, create a key as above. If you want to make groups with the same month for all years, and subgroups per year:
// parameter keySelector: make groups with same month, for all year:
statMonth => stathMonth.m_Date.Month,
// parameter resultSelector: take the key and all statMonths with this key to make a new
(month, statMonthsWithThisMonth) => new
{
Month = month,
SubGroups = statMonthsWithThisMonth.GroupBy(
// make subgroups with same Year,
// all statMonths in this group already have the same month
statMonth => statMonth.m_Date.Year,
// parameter resultSelector: use the year, and all statMonths of the group
// with this year
(year, statMonthisWithThisYear) => new
{
Year = year,
... // other statMonth
})
})
You didn't say what you wanted to do other than in your example, so I don't know whether it is better to make a sequence of groups with same year/month combination or a sequence with same month of sub groups with same year.
IMHO the later makes it more difficult to see what happens without adding much functionality.

Related

How would one create summary columns in query results using Linq-to-SQL?

Let's say we have these tables:
CAR
ID Name
1 Mustang
2 Taurus
CAR_PART
ID CAR_ID PART_NUMBER
1 1 M772
2 1 A443
3 2 Z889
CAR_COLOR
ID CAR_ID COLOR_NAME
1 1 Red
2 1 Blue
3 2 Yellow
We need to use Linq-to-SQL to get this result:
CAR_ID CAR_NAME CAR_PART_LIST CAR_COLOR_LIST
1 Mustang M772,A443 Red,Blue
How would this be accomplished? I have a new class created with the result column names, and figure a select new MyClass{}; at the end would be good, but am not sure how to handle the multiple groupings for the CAR_PART_LIST and CAR_COLOR_LIST columns.
Any ideas?
edit: here is what I have so far:
from car in db.CAR
join part in db.CAR_PART on car.ID equals part.CAR_ID
join color in db.CAR_COLOR on car.ID equals color.CAR_ID
where car.ID = 1
select new MySearchResult{
CAR_ID = car.ID,
CAR_NAME = car.Name,
CAR_PART_LIST = ?,
CAR_COLOR_LIST = ?
}
public class MySearchResult{
public int CAR_ID { get; set; }
public string CAR_NAME { get; set; }
public string CAR_PART_LIST { get; set; }
public string CAR_COLOR_LIST { get; set; }
public MySearchResult() { }
}
Using the obvious String extension method:
public static string Join(this IEnumerable<string> s, string sep) => String.Join(s, sep);
You can compute the answer by using group join on each related table:
var ans = from car in db.CAR
join part in db.CAR_PART on car.ID equals part.CAR_ID into partj
join color in db.CAR_COLOR on car.ID equals color.CAR_ID into colorj
where car.ID == 1
select new MySearchResult {
CAR_ID = car.ID,
CAR_NAME = car.Name,
CAR_PART_LIST = partj.Select(p => p.PART_NUMBER).Join(","),
CAR_COLOR_LIST = colorj.Select(c => c.COLOR_NAME).Join(",")
};
Do you have foreign keys set up for db.CAR_PART and db.CAR_COLOR? If so, that linq-to-sql will automatically give you properties for the joins. So, it becomes:
var q = from car in db.Car
where car.ID == 1
select new MySearchResult
{
CAR_ID = car.ID,
CAR_NAME = car.Name,
CAR_PART_LIST = String.Join(",", car.CAR_PARTs.Select(cp=>cp.PART_NUMBER))
CAR_PART_LIST = String.Join(",", car.CAR_COLORs.Select(cp=>cp.COLOR_NAME))
};
So you have a table of CarParts, where every CarPart has a CarId and a PartNumber; and you have a table of CarColours, where every CarColour has a Carid and a ColourName.
I assume you do not support invisible cars, so every car has at least one part, and one colour.
You want a sequence of all CarIds, each CarId with the CarName, a list of all CarParts belonging to this CarId(= that have this CarId as foreign key) and a list of all ColourNames belonging to this CarId (again using the foreign key.
To do this, first we get all CarIds with their CarParts and all CarIds with their ColourNames, then we can Join the results on common CarId.
If you think there might be cars without parts or without colours, you need to do a 'Full outer Join' instead of a normal Join. This is not part of standard LINQ, but you can write the extension function yourself. See LINQ Full Outer Join
After the join on common CarId, we Join the result with your Cars on CarId
var partsGroupedByCarId = carParts.GroupBy( // make groups of carParts
carPart => carPart.CarId); // with common CarId as Key
var coloursGroupedByCarId = carColours.GroupBy( // make groups of carColours
carColour => carColour.CarId);, // with common CarId as Key
var joinResult = partsGroupedByCarId.Join( // Join the two groups
coloursGroupedByCarId,
partGroup => partGroup.Key, // from each group of parts take the key (= CarId)
colourGroup => // from each group of colours take the key (= CarId)
(partGroup, colourGroup) => new // when they match make a new object
{
CarId = partGroup.Key, // with the common CarId
CarParts = partGroup // all partNumbers of the car with CarId
.Select(item => item.PartNumber),
CarColours = colourGroup // all colourNames of the car with carId
.Select(item => item.ColourName),
});
Finally a Join of the Cars with all their Colours and Parts:
var result = Cars.Join(joinResult, // Join all cars with the joinResult
car => Id, // from every car take the id
joinedItem => joinedItem.CarId, // from every joinedItem take the CarId
(car, joinedItem) => new // for every car with its matching joinedItem
{ // make a new object
Id = car.Id,
Name = car.Name,
Parts = joinedItem.CarParts.ToList(),
Colours = joinedItem.CarColours.ToList(),
});
TODO: consider creating one big LINQ statements. As all statements use deferred execution I don't think this will improve efficiency. It certainly will decrease readability.
I writed it here dotnetfiddle please check it out:
var q = from car in cars
join part in carparts on car.ID equals part.CAR_ID into parts
join color in carcolors on car.ID equals color.CAR_ID into clrs
where car.ID == 1
select new MySearchResult{
CAR_ID = car.ID,
CAR_NAME = car.Name,
CAR_PART_LIST = String.Join(",",parts.Select(p => p.PART_NUMBER)),
CAR_COLOR_LIST = String.Join(",",clrs.Select(c => c.COLOR_NAME))};
foreach (var item in q)
Console.WriteLine("{0} {1} {2} {3}",
item.CAR_ID,
item.CAR_NAME,
item.CAR_PART_LIST,
item.CAR_COLOR_LIST);

LINQ: Finding an item in a List which contains List of items

guys!
I have a small issue with LINQ (Im total beginer in this topic). Maybe it is some desing mistake, but let you decide it.
I'm coding a Windows Store App, which is kind a calendar. It has a Day object. Because of the semantic zoom (and some groupping hack), I put this Day into a wrapper class, named as Month.
After loading all data, and after getting the current data, I want to extract from this structure the current Day object.
Here is the important code:
public class Day
{
public int nr { get; set; }
...
}
public class Month
{
public string Title {get;set;}
public List<Day> Days{get;set;}
}
Later I have this:
List<Month> Months;
It is correctly filled with lists of days. Now comes the tricky part:
Day Today = Months.Find( ??? )
I had some idea, but none of them was statisfying...
So, the question is:
How can I select an item from a multiple list hierarchy in LINQ?
(List<List<Day>>, and one condition must met in each list (Day.nr and Month.nr))
Create an Enum for every month:
public enum NamesOfMonths
{
January = 1,
February = 2,
// so on and so forth
}
Now, you can use it to find the correct Month, and eventually the correct Day.
var dayToday = DateTime.Now.Day;
var monthToday = DateTime.Now.Month;
Day Today = Months.Find(m => m.Title.Equals(((NamesOfMonths)monthToday).ToString()))
.Days.Where(d => d.Nr == dayToday).FirstOrDefault();
I think you're looking for SelectMany:
var days = months.SelectMany(m => m.Days); // Gets all the days in those months
var today = days.Where(d => /* some condition goes here */);

How to Group Data by week in Linq

I have data starting from date 01/02/2011 and get updated every day.
i want to format data using week starting from date 01/02/2011.
I have entities
public DateTime? MeasurementDate { get; set; }
FORMAT OF DATE:2011-02-01 00:00:00.0000000
Which gives me date from database ..
I want to group data in linq BY Week?
Can You help me?
If you're using Linq-To-Entities you can do:
source.GroupBy(x => SqlFunctions.DatePart("ww", x.MeasurementDate));
Use the GetWeekOfYear method of the Calendar class
var dt = new List<DateTime?> { /*...*/ };
var dfi = DateTimeFormatInfo.CurrentInfo;
var ordered = dt
.Where(x => x.HasValue)
.OrderBy(x => dfi.Calendar.GetWeekOfYear(x.Value, CalendarWeekRule.FirstDay, DayOfWeek.Monday));
Or you can get the date of the first day in the week then group by that date.
To get the date of the first day in the week.
you can use this code:
public static class DateTimeExtensions
{
public static DateTime StartOfWeek(this DateTime dt, DayOfWeek startOfWeek)
{
int diff = dt.DayOfWeek - startOfWeek;
if (diff < 0)
{
diff += 7;
}
return dt.AddDays(-1 * diff).Date;
}
}
then you can group by the first date of the week like this:
source.GroupBy(i => i.MeasurementDate.StartOfWeek(DayOfWeek.Monday));
reference

Linq to Objects - Left Outer Join Distinct Object Property Values to an Aggregate Count

Lets say I have a generic list of the the following objects:
public class Supermarket
{
public string Brand { get; set; }
public string Suburb { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
So using a List<Supermarket> which is populated with many of these objects with different values I am trying to:
Select the distinct Suburb properties from a
superset of Supermarket objects contained in a List<Supermarket> (say this superset contains 20
distinct Suburbs).
Join the Distinct List of Suburbs above to another set of aggregated and counted Suburbs obtained by a LINQ query to a different, smaller list of List<Supermarket>
The distinct items in my superset are:
"Blackheath"
"Ramsgate"
"Penrith"
"Vaucluse"
"Newtown"
And the results of my aggregate query are:
"Blackheath", 50
"Ramsgate", 30
"Penrith", 10
I want to join them to get
"Blackheath", 50
"Ramsgate", 30
"Penrith", 10
"Vaucluse", 0
"Newtown", 0
Here is what I have tried so far:
var results = from distinctSuburb in AllSupermarkets.Select(x => x.Suburb).Distinct()
select new
{
Suburb = distinctSuburb,
Count = (from item in SomeSupermarkets
group item by item.Suburb into aggr
select new
{
Suburb = aggr.Key,
Count = aggr.Count()
} into merge
where distinctSuburb == merge.Suburb
select merge.Count).DefaultIfEmpty(0)
} into final
select final;
This is the first time I have had to post on Stack Overflow as its such a great resource, but I can't seem to cobble together a solution for this.
Thanks for your time
EDIT: OK So I solved this a short while after the initial post. The only thing I was missing was chaining a call to .ElementAtOrDefault(0) after the call to .DefaultIfEmpty(0). I also verifed that using .First() instead of .DefaultIfEmpty(0) as Ani pointed out worked, The correct query is as follows:
var results = from distinctSuburb in AllSupermarkets.Select(x => x.Suburb).Distinct()
select new
{
Suburb = distinctSuburb,
Count = (from item in SomeSupermarkets
group item by item.Suburb into aggr
select new
{
Suburb = aggr.Key,
Count = aggr.Count()
} into merge
where distinctSuburb == merge.Suburb
select merge.Count).DefaultIfEmpty(0).ElementAtOrDefault(0)
} into final
select final;
LASTLY: I ran Ani's code snippet and confirmed that it ran successfully, so both approaches work and solve the original question.
I don't really understand the assumed equivalence between State and Suburb (where distinctSuburb == merge.State), but you can fix your query adding a .First() after the DefaultIfEmpty(0) call.
But here's how I would write your query: using a GroupJoin:
var results = from distinctSuburb in AllSupermarkets.Select(x => x.Suburb).Distinct()
join item in SomeSupermarkets
on distinctSuburb equals item.Suburb
into suburbGroup
select new
{
Suburb = distinctSuburb,
Count = suburbGroup.Count()
};

LINQ (Dynamic): OrderBy within a GroupBy using dynamic linq?

I had the following query using normal linq and it was working great (using anonymous type),
var result = from s in Items
group s by s.StartTime into groupedItems
select new {groupedItems.Key, Items= groupedItems.OrderBy(x => x.Name) };
But using Dynamic Linq I cannot get it to order by within the groupby.
result = Items.GroupBy("StartTime", "it").OrderBy("Name");
It states the Name isn't available. It is worth noting that if I take my OrderBy off, everything works great but items inside each "Key" are not ordered.
This is a good question!
I simulated your situation by creating a class called Item.
public class Item
{
public DateTime StartTime { get; set; }
public string Name { get; set; }
}
and then created a basic list of items to do the groupby.
List<Item> Items = new List<Item>()
{
new Item() { StartTime = DateTime.Today, Name = "item2"},
new Item() { StartTime = DateTime.Today, Name = "item1"},
new Item() { StartTime = DateTime.Today.AddDays(-1), Name = "item3"},
};
Now the big difference in the 2 queries is where the order by is being performed. In the first query, when you perform groupedItems.OrderBy(x => x.Name) its being performed on a IGrouping<DateTime,Item> or a single entry as it iterates through all the groupings.
In the second query, the orderby is being performed after the fact. This means you're doing an orderby on a IEnumerable<IGrouping<DateTime,Item>> because the iterations have already happened.
Since Microsoft was nice they added something to help deal with this for expressions. This overload allows you to specify the item returned as it iterates through the collection. Here's an example of the code:
var expressionResult = Items.GroupBy(x => x.StartTime,
(key, grpItems) => new { key, Items = grpItems.OrderBy(y => y.Name) });
The second part of the GroupBy you can specify a lambda expression that takes a key and a grouping of items under that key and return an entry that you specify, which is the same as you're doing in the original query.
Hope this helps!

Resources