Add rows with same ID together after SELECT using LINQ - c#

Hi all I am using LINQ to query a table like this:
AlbumID | Spotify | iTunes
--------------------------
5 | 12 | 4
5 | 18 | 6
6 | 10 | 8
I would like to add together rows with the same unique AlbumID, I am aware of GroupBy but that groups together all similar IDs so I used Where that uses albumID (an input arg in this example it could be 5)
var platformArray = Context.Analytics.Where(x => x.AlbumID == albumId).Select(s => new
{
Id = s.AlbumID,
Spotify = s.SpotifyClicks,
Itunes = s.ItunesClicks
});
But when I print out to debug console, it does not combine the unique ID's
foreach (var item in platformArray)
{
Debug.WriteLine(item.Id);
Debug.WriteLine(item.Spotify);
Debug.WriteLine(item.Itunes);
}
I see (added arrows for clarity):
5 <-ID
12 <-Spotify
4 <-ITunes
5 <-ID
18 <-Spotify
6 <-ITunes
How can I reformat my LINQ code to print out something like:
5 <-ID
30 <-Spotify
10 <-Itunes
Thanks!
EDIT: adding my Analytic Class
public class Analytic
{
private object p1;
private object p2;
private object p3;
public Analytic(object p1, object p2, object p3)
{
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
}
[Key]
public int Id { get; set; }
public int AlbumID { get; set; }
public int SpotifyClicks { get; set; }
public int ItunesClicks { get; set; }
public virtual Album Album { get; set; }
}

Try below code:
var List = from element in prod
group element by new { element.Id } into g
select new yourClass
(
Id = g.First().ID,
Spotify = g.Sum(s => s.Spotify)
Itunes = g.Sum(s => s.Itunes)
);

So I managed to figure this out from the help of Hasan's comment on my original post and some additional fixes:
var query = Context.Analytics
.Where(x => x.AlbumID == albumId)
.GroupBy(c => c.AlbumID)
.Select(
g =>
new
{
Id = g.Key,
Spotify = g.Sum(s => s.SpotifyClicks),
ITunes = g.Sum(s => s.ItunesClicks),
}
);
This only queries for my specific albumID and sums together all rows with that specific ID!
Thanks everyone

So after you have grouped your Albums into groups of Albums with the same AlbumId, you want to transform all Albums in the group into one object, containing the common AlbumId, the total of all Spotify of all Albums in your group, and the total of all iTunes of all Albums in your group.
var result = albums.GroupBy(album => album.AlbumId)
// from every group, make one element with the sum of Spotify and sum of itunes:
.Select(group => new
{
AlbumId = group.Key,
Spotify = group.Select(groupElement => groupElement.Spotify).Sum(),
ITunes = group.Select(groupElement => groupElement.Itunes).Sum(),
});
Simple comme bonjour!

Related

LINQ Select IDs from multiple levels

I'd like to get one list of IDs from all nested entities.
Code:
// Entities
class Floor
{
public int Id { get; set; }
public ICollection<Room> Rooms { get; set; } = new List<Room>();
}
class Room
{
public int Id { get; set; }
public ICollection<Chair> Chairs { get; set; } = new List<Chair>();
}
class Chair
{
public int Id { get; set; }
}
// Setup
var floor = new Floor() { Id = 1000 };
var room = new Room() { Id = 100 };
var chair = new Chair() { Id = 10 };
room.Chairs.Add(chair);
floor.Rooms.Add(room);
var floors = new List<Floor>() { floor };
// Select all IDs
var ids = floors.???
Expected result:
{ 10, 100, 1000 }
What I've tried. It selects IDs only from the deepest level, not all of them:
// Select all IDs
var ids = floors
.SelectMany(f => f.Rooms)
.SelectMany(r => r.Chairs)
.Select(ch => ch.Id)
.ToList();
SelectMany is what you need together with Append:
var ids = floors
.SelectMany(f => f.Rooms
.SelectMany(r => r.Chairs
.Select(c => c.Id).Append(r.Id)).Append(f.Id));
Your current code flattens the hierarchy to collection of Chair and selects only their ids.
With pure LINQ you can do via nesting SelectMany and using Append/Prepend:
var ids = floors
.SelectMany(f => f.Rooms
.SelectMany(r => r.Chairs
.Select(ch => ch.Id) // select chairs ids
.Append(r.Id)) // append "current" room id to it's collection of chair ids
.Append(f.Id)) // append "current" floor id to it's collection of rooms and chairs ids
.ToList();
An ugly hack would be to append "fakes" to the child rooms/chairs with the ID of their parent:
var ids = floors
.SelectMany(f => f.Rooms.Append(new Room { Id = f.Id }))
.SelectMany(r => r.Chairs.Append(new Chair { Id = r.Id }))
.Select(ch => ch.Id)
.ToList();
It can be done with a recursive helper function like this:
IEnumerable<int> CollectIds<T>(T x) => x switch {
Floor f => f.Rooms.SelectMany(CollectIds).Prepend(f.Id),
Room r => r.Chairs.SelectMany(CollectIds).Prepend(r.Id),
Chair c => Enumerable.Repeat(c.Id, 1),
_ => throw new NotSupportedException()
};
Usage:
var ids = floors
.SelectMany(CollectIds)
.ToList();
You could consider extracting this function into a common interface to avoid the awkward type switch and the recursion.

How can I order this simple .NET list by two properties?

Given a list of Location's, I need to do 2 ordering steps.
Order by LocationType ascending.
For each location type value order these results by the Name ascending.
Sample data:
Location Type | Name
2 | Templestowe Lower
2 | Templestowe
1 | Melbourne
Expected results:
1. Melbourne
2. Templestowe
3. Templestowe Lower
Here is a (not working) .NET Fiddle...
and here's the main code (copied from that fiddle)..
private class Location
{
public Location (string name, int locationType)
{
Name = name;
LocationType = locationType;
}
public string Name { get; private set; }
public int LocationType { get; private set;}
}
public static void Main()
{
var locations = new List<Location>
{
new Location("Templestowe Lower", 2),
new Location("Templestowe", 2),
new Location("Melbourne", 1)
};
// TODO: return a list, sorted by LocationType and name.
var results = XXXXX;
foreach(var location in results)
{
Console.WriteLine(location.Name);
}
}
locations.Sort((x,y) => {
int delta = x.LocationType.CompareTo(y.LocationType);
if(delta == 0) delta = string.Compare(x.Name, y.Name);
return delta;
});
Alternatively:
var results = locations.OrderBy(x => x.LocationType).ThenBy(x => x.Name);
Or in LINQ syntax (compiles to the same thing):
var results = from loc in locations
orderby loc.LocationType, loc.Name
select loc;
Check this using group by
var results = locations
.GroupBy(x => x.LocationType)
.OrderBy(x => x.Key)
.SelectMany(g => g.OrderBy(x=>x.Name));

How can I select from an included entity in LINQ and get a flat list similar to a SQL Join would give?

I have two classes:
public class Topic
{
public Topic()
{
this.SubTopics = new HashSet<SubTopic>();
}
public int TopicId { get; set; }
public string Name { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public class SubTopic
public int SubTopicId { get; set; }
public int Number { get; set; }
public int TopicId { get; set; }
public string Name { get; set; }
public virtual Topic Topic { get; set; }
}
What I would like to do is to get a Data Transfer Object output from LINQ that will show me. I do want to see the TopicId repeated if there is more than one SubTopic inside that topic:
TopicId Name SubTopicId Name
1 Topic1 1 SubTopic1
1 Topic1 2 SubTopic2
1 Topic1 3 SubTopic3
2 Topic2 4 SubTopic4
I tried to code a Linq statement like this:
var r = context.Topics
.Select ( s => new {
id = s.TopicId,
name = s.Name,
sid = s.SubTopics.Select( st => st.SubTopicId),
sidname = s.SubTopics.Select ( st => st.Name)
}).
ToList();
But this does not really work as it returns sid and sidname as lists.
How will it be possible for me to get a flat output showing what I need?
You need SelectMany to expand a nested collection, along these lines
var r = context.Topics.SelectMany(t => t.SubTopics
.Select(st => new
{
TopicID = t.TopicId,
TopicName = t.Name,
SubTopicID = st.SubTopicId,
SubTopicName = st.Name
}));
try this :
var r = context.Topics
.Select ( s => new {
id = s.TopicId,
name = s.Name,
sid = s.SubTopics.Where(st=>st.TopicId==s.TopicId).Select( st => st.SubTopicId ),
sidname = s.SubTopics..Where(st=>st.TopicId==s.TopicId).Select ( st => st.Name)
}).
ToList();
Hope it will help
#Sweko provided an answer that satisfies the exact output that you requested. However, this can be even simpler if you just return the subtopic intact. It may run a bit quicker as well, since you don't need to create a new object for each element in the result.
Lastly, it looks like you wanted your result set ordered. For completeness, I've added those clauses as well.
var r = context.Topics
.SelectMany( topic => topic.SubTopics )
.OrderBy(sub => sub.TopicId)
.ThenBy(sub => sub.SubTopicId);

How to merge two lists that share 1 attribute into a third one with LINQ?

I'd wish to create a list of the following class (List):
public class StatusData
{
public DateTime? Date { get; set; }
public int RegisteredUsers { get; set; }
public int Orders { get; set; }
public decimal Import { get; set; }
}
This list should be filled with with information of another 2 lists: a List<Contact> and a List<Purchase>. Both classes have the following shape:
public class Contact
{
public DateTime? RegisterDate { get; set; }
public string Name { get; set; }
}
public class Purchase
{
public DateTime? PurchaseDate { get; set; }
public int PurchaseID {get;set;}
public int Import { get; set; }
}
The idea is that the List should, for every day, which is the amount of Registered users, the amount of purchases and the total import of these purchases. So, in essence, both just share the Date.
However, I can't come with a good LINQ expression to create the List. One solution I considered is to retrieve all the distinct dates among the 2 lists and then iterate over them to retrieve the information I want, but I think this approach is rather "ugly". Is there any way to do what I want to do easily?
EDIT: An example of what I want would be the following:
Contacts
--------
Name RegisteredDate
David 10/10/2013
Paul 09/10/2013
Gina 10/10/2013
Roger 09/10/2013
Rose 05/10/2013
Jean 07/10/2013
Mark 04/10/2013
Lisa 04/10/2013
Purchases
-----------
ID PurchaseDate Import
1 10/10/2013 10
2 10/10/2013 10
3 10/10/2013 20
4 04/10/2013 15
5 04/10/2013 15
6 07/10/2013 20
7 07/10/2013 2
8 07/10/2013 2
Expected result
----------------
Date RegisteredUsers Purchases Import
04/10/2013 2 2 30
05/10/2013 1 0 0
07/10/2013 1 3 24
09/10/2013 2 0 0
10/10/2013 2 3 40
Kind regards
var contacts = new List<Contact>();
var purchases = new List<Purchase>();
var dates = contacts.Select(x => x.RegisterDate.Value)
.Concat(purchases.Select(x => x.PurchaseDate.Value))
.Distinct();
var data = from date in dates
join c in contacts on date equals c.RegisterDate.Value into registered
join p in purchases on date equals p.PurchaseDate.Value into purchased
select new StatusData {
Date = date,
RegisteredUsers = registered.Count(),
Orders = purchases.Count(),
Import = purchases.Sum(x => x.Import)
};
It will return StatusData for all days, when at least one registration OR one purchase was made.
So there are several separate tasks here. The first thing that you're doing is grouping your contacts by the registration date. Next, you're joining that result to the Purchases table based on the date, aggregating count/sums on two different columns. LINQ has a method for each of these two operations:
var query = from contact in contacts
group contact by contact.RegisterDate into contactGroup
join purchase in purchases
on contactGroup.Key equals purchase.PurchaseDate into purchaseGroup
select new StatusData
{
Date = contactGroup.Key,
RegisteredUsers = contactGroup.Count(),
Orders = purchaseGroup.Count(),
Import = purchaseGroup.Sum(p => p.Import),
};
This is really two separate queries with their results merged at the end.
var contact = from c in Contact
group c by c.RegisterDate into c
select new StatusData
{
Date = c.Key,
RegisteredUsers = c.Count(),
Orders = 0,
Import = 0,
};
var purchases = from p in Purchases
group p by p.PurchaseDate into p
select new StatusData
{
Date = p.Key,
RegisteredUsers = 0,
Orders = p.Count(),
Import = p.Sum(x => x.Import),
};
var final = from x in contact.Concat(purchases)
group x by x.Date into x
select new StatusData
{
Date = x.Key,
RegisteredUsers = x.Sum(y => y.RegisteredUsers),
Orders = x.Sum(y => y.Orders),
Import = x.Sum(y => y.Import),
};

How to do a Pivot in Linq [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
How can i made a pivot for this
I have a table as provided below
ID | value | Name
--------+---------------+------------------
1 Digital Model
1 companyA Manufacturer
2 Analog Model
2 CompanyB Manufacturer
3 Fiber Model
3 CompanyC Manufacturer
I need to convert this back to
ID | Model | Manufacturer
--------+--------------+-------------------
1 Digital companyA
2 Analog CompanyB
3 Fiber CompanyC
Please help me with the Linq Query for this. Using T-SQL, I am able to convert it properly. However, with Linq I am finding problems writing Query.
Try this:
var results = Data.GroupBy(l => l.Name);
.SelectMany( g =>
new
{
Metadata = g.Key,
data = g
});
var pivoted = new List<PivotedEntity>();
foreach(var item in results)
{
pivoted.Add(
new PivotedEntity
{
Id = item.Id,
Model = item.data.Where(x => x.Name == "Model")
.FirstOrDefault().value,
Manufacturer = item.data.Where(x => x.Name == "Manufacturer")
.FirstOrDefault().value,
});
}
You should first define a new class:
public class PivotedEntity
{
public int Id {get; set;}
public string Model {get; set;}
public string Manufacturer {get; set;}
}

Categories