I want to display each book average rating with its publisher using LINQ and Lambda.
Here's my book list
private List<Book> Books = new List<Book>(){
new Book {
Id: 1,
Name: Book A,
PublisherId: 1
}, new Book {
Id: 2,
Name: Book B,
PublisherId: 2
}, new Book {
Id: 3,
Name: Book C,
PublisherId: 1
}
};
First, i Join Book list with Publisher list using this query
var bookPublisher = Books.SelectMany( book => Publishers.Where(publisher => book.PublisherId == publisher.Id),
(book, publisher) => new { book, publisher });
Then, i try to use left join with BookTransaction list to get detail rating for each book. Here's my BookTransaction list.
private List<Book_Transaction> BookTransactions = new List<Book_Transaction>() {
new Book_Transaction {
Id = 1,
BookId = 1,
CustomerId = 1,
RatingStar = 4.5
}, new Book_Transaction {
Id = 2,
BookId = 2,
CustomerId = 1,
RatingStar = 4
}, new Book_Transaction {
Id = 3,
BookId = 1,
CustomerId = 2,
RatingStar = 5
},new Book_Transaction {
Id = 4,
BookId = 2,
CustomerId = 2,
RatingStar = 3.5
},new Book_Transaction {
Id = 5,
BookId = 1,
CustomerId = 3,
RatingStar = 4
}};
and here's my query
var bookPublisherRating = bookPublisher.SelectMany(bp => BookTransactions.Where(bt => bp.book.Id == bt.BookId).DefaultIfEmpty(),
(bp, bt) => new { BookPublish = bp, BookRating = bt.RatingStar });
in that query, i got the error saying that "'Object reference not set to an instance of an object.' bt was null."
I tried using different approach, but the result it just the same error.
var bookPublisherRating = bookPublisher.GroupJoin(BookTransactions,
bp => bp.book.Id,
bt => bt.BookId,
(bp, bt) => new { BookPublish = bp, BookTrans = bt })
.SelectMany(temp => temp.BookTrans.DefaultIfEmpty(),
(temp, y) => new { BookPublish = temp.BookPublish, bookRating = y.RatingStar });
Can someone tell me where i was wrong? When i deleted the DefaultIfEmpty function, it can display the data but only where both list have the bookId value included, like this
| BookName | PublisherName | Rating |
| Book A | Publisher A | 4.5 |
| Book A | Publisher A | 5 |
| Book A | Publisher A | 4 |
| Book B | Publisher B | 4 |
| Book B | Publisher B | 3.5 |
I expect the result of the query just like this
| BookName | PublisherName | Rating |
| Book A | Publisher A | 4.5 |
| Book B | Publisher B | 3.75 |
| Book C | Publisher A | 0 |
Linq has an Average Method. You can call it on a collection of numeric values, or for a collection of objects you can pass it the property name to average. You can also call GroupBy to create key<->collection pairs.
So you can group your data by the BookId and find the average rating.
Assuming the following
public class Book_Transaction
{
public int Id { get; set; }
public int BookId { get; set; }
public int CustomerId { get; set; }
public double RatingStar { get; set; }
}
var bookTransactions = new List<Book_Transaction>() {
new Book_Transaction {
Id = 1,
BookId = 1,
CustomerId = 1,
RatingStar = 4.5
}, new Book_Transaction {
Id = 2,
BookId = 2,
CustomerId = 1,
RatingStar = 4
}, new Book_Transaction {
Id = 3,
BookId = 1,
CustomerId = 2,
RatingStar = 5
},new Book_Transaction {
Id = 4,
BookId = 2,
CustomerId = 2,
RatingStar = 3.5
},new Book_Transaction {
Id = 5,
BookId = 1,
CustomerId = 3,
RatingStar = 4
}};
Then
bookTransactions.GroupBy(
bt => bt.BookId,
(bookId, collection) => new {
BookId = bookId,
AverageRating = collection.Average(x => x.RatingStar)
}).ToList()
Gives the following output in the C# interactive console:
List<<>f__AnonymousType0#5<int, double>>(2) {
{ BookId = 1, AverageRating = 4.5 },
{ BookId = 2, AverageRating = 3.75 }
}
Related
I currently have the following classes:
public class NavigationItem
{
public int ID { get; set; }
public string Title { get; set; }
public int ParentID { get; set; }
public List<NavigationItem> Children { get; set; }
}
public class FlatItem
{
public int ID { get; set; }
public string Title { get; set; }
public int ParentID { get; set; }
}
I have a sample data as follows:
+====+============+==========+
| ID | Title | ParentID |
+====+============+==========+
| 1 | Google | |
+----+------------+----------+
| 2 | Microsoft | |
+----+------------+----------+
| 3 | Oracle | |
+----+------------+----------+
| 4 | Gmail | 1 |
+----+------------+----------+
| 5 | Sheets | 1 |
+----+------------+----------+
| 6 | Adsense | 1 |
+----+------------+----------+
| 7 | Azure | 2 |
+----+------------+----------+
| 8 | SharePoint | 2 |
+----+------------+----------+
| 9 | Office | 2 |
+----+------------+----------+
| 10 | Java | 3 |
+----+------------+----------+
| 11 | Word | 9 |
+----+------------+----------+
| 12 | Excel | 9 |
+----+------------+----------+
| 13 | PowerPoint | 9 |
+----+------------+----------+
I already have the code to pull all the information from the sample data above and turn it into a List<FlatItem> object.
What's the best approach so that I can have a List<NavigationItem> object which will look like something below:
Google
Gmail
Sheets
AdSense
Microsoft
Azure
SharePoint
Office
Word
Excel
PowerPoint
Oracle
Java
I'm thinking of creating a recursive method to loop through my List<FlatItem> then structure it in a way to be a nested list of NavigationItem.
No need for recursion. You could use LINQ to build the structure easily:
List<FlatItem> flatItems = ...;
var navigationItems = flatItems.Select(
i => new NavigationItem { ID = i.ID, Title = i.Title, ParentID = i.ParentID }
).ToList();
foreach (var i in navigationItems)
i.Children = navigationItems.Where(n => n.ParentID == i.ID).ToList();
// get Google, Microsoft, Oracle items
var rootNavigationItems = navigationItems.Where(n => n.ParentID == 0);
Try this:
List<FlatItem> source = new List<UserQuery.FlatItem>()
{
new FlatItem() { ID = 1, Title = "Google", ParentID = null },
new FlatItem() { ID = 2, Title = "Microsoft", ParentID = null },
new FlatItem() { ID = 3, Title = "Oracle", ParentID = null },
new FlatItem() { ID = 4, Title = "Gmail", ParentID = 1 },
new FlatItem() { ID = 5, Title = "Sheets", ParentID = 1 },
new FlatItem() { ID = 6, Title = "Adsense", ParentID = 1 },
new FlatItem() { ID = 7, Title = "Azure", ParentID = 2 },
new FlatItem() { ID = 8, Title = "SharePoint", ParentID = 2 },
new FlatItem() { ID = 9, Title = "Office", ParentID = 2 },
new FlatItem() { ID = 10, Title = "Java", ParentID = 3 },
new FlatItem() { ID = 11, Title = "Word", ParentID = 9 },
new FlatItem() { ID = 12, Title = "Excel", ParentID = 9 },
new FlatItem() { ID = 13, Title = "PowerPoint", ParentID = 9 },
};
var lookup = source.ToLookup(x => x.ParentID);
Func<int?, List<NavigationItem>> build = null;
build = pid =>
lookup[pid]
.Select(x => new NavigationItem()
{
ID = x.ID,
Title = x.Title,
ParentID = x.ParentID,
Children = build(x.ID)
})
.ToList();
To start the process call build(null). That gives me this:
This does assume that the ParentId property is a int? - which your data table does suggest.
If you are ok with using recursion, you can create a function like this:
public List<NavigationItem> ChildrenOf(List<FlatItem> flatItems, int parentId)
{
var childrenFlatItems = flatItems.Where(i => i.ParentID == parentId);
return childrenFlatItems.Select(i => new NavigationItem {
ID = i.ID,
Title = i.Title,
ParentID = i.ParentID,
Children = ChildrenOf(flatItems, i.ID)})
.ToList();
}
Then, assuming that your root items have a parent id of 0 (since you aren't using nullable types), you generate the full list with:
ChildrenOf(flatsItems, 0)
Untested, however you could try this, should be fairly fast as well
var list = new List<FlatItem>();
var result = new List<NavigationItem>();
// just a helper to remember ids
var dict = new Dictionary<int, NavigationItem>();
foreach (var item in list)
{
var nav = new NavigationItem()
{
ID = item.ID,
ParentID = item.ParentID,
Title = item.Title,
Children = new List<NavigationItem>()
};
if (!dict.ContainsKey(nav.ParentID))
result.Add(nav);
else
dict[nav.ParentID].Children.Add(nav);
dict.Add(item.ID, nav);
}
no recursive, just GroupBy.
List<NavigationItem> list = ... // map from List<FlatItem>
// and init Children = new List<NavigationItem>();
var groups = list.GroupBy(x => x.ParentID).ToList();
foreach (var g in groups)
{
var items = list.Find(x => x.ID == g.Key);
if (items != null)
items.Children = g.ToList();
}
// tops is [Google, Microsoft, Oracle]
var tops = list.Where(x => x.ParentID == null).ToList();
This question already has an answer here:
Take(limit) list inside Groupby in Entity Framework
(1 answer)
Closed 5 years ago.
I need to take (for example, 2), 2 messages from a conversation
example:
id = idConversation
Id | messageId | Message
---|-----------|--------
1 | 1 | "asd"
1 | 2 | "asd2"
1 | 3 | "asd3"
1 | 4 | "asd4"
2 | 5 | "asd5"
3 | 6 | "asd6"
3 | 7 | "asd7"
3 | 8 | "asd8"
3 | 9 | "asd9"
3 | 10 | "asd10"
4 | 11 | "asd11"
4 | 12 | "asd12"
4 | 13 | "asd13"
and i want that
Id messageId Message
---|-----------|--------
1 | 1 | "asd"
1 | 2 | "asd2"
2 | 5 | "asd5"
3 | 6 | "asd6"
3 | 7 | "asd7"
4 | 11 | "asd11"
4 | 12 | "asd12"
i can grouby idConversation, but i cant limit quantity using grouby in a conversation.
var test = unitOfWork.ChatMensagemRepository.GetAll()
.Where(x => x.PessoaCodigoPessoa == codigoRemetente)
.GroupBy(x => x.ChatConversaCodigoChatConversa)
.Select(group => new
{
codigoChat = group.Key,
list = group.Select(mensagem => new
{
// do stuff
})
}).ToList();
this is ok... but dont limit my list, when i do group.take(2).Select.....
give me "Subquery returns more than 1 row"
var test = unitOfWork.ChatMensagemRepository.GetAll()
.Where(x => x.PessoaCodigoPessoa == codigoRemetente)
.GroupBy(x => x.ChatConversaCodigoChatConversa)
.Select(group => new
{
codigoChat = group.Key,
list = group.Take(2).Select(mensagem => new
{
// do stuff
})
}).ToList();
error : Subquery returns more than 1 row
var test = unitOfWork.ChatMensagemRepository.GetAll()
.Where(x => x.PessoaCodigoPessoa == codigoRemetente)
.GroupBy(x => x.ChatConversaCodigoChatConversa)
.Select(group => new
{
codigoChat = group.Key,
list = group.Select(mensagem => new
{
// do stuff
}).take(2)
}).ToList();
error : Subquery returns more than 1 row
Here's an example of what I think you're after. This query will return the top 3 sudtents by GPA in each class:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ef6Test
{
class Student
{
public int Id { get; set; }
public string Name { get; set; }
public string Class{ get; set; }
public decimal GPA { get; set; }
}
class Db : DbContext
{
public DbSet<Student> Students { get; set; }
}
class Program
{
static void Main(string[] args)
{
using (var db = new Db())
{
var q = db.Students
.GroupBy(s => s.Class)
.SelectMany(g => g.OrderByDescending(s => s.GPA).Take(3));
Console.WriteLine(q.ToString());
Console.ReadKey();
}
}
}
}
I tried this code and is working fine:
class Conversation
{
public int Id;
public string Message;
public int MessageId;
}
class Program
{
static void Main(string[] args)
{
var inputList = new List<Conversation>
{
new Conversation() {Id = 1, Message = "asd0", MessageId = 1},
new Conversation() {Id = 1, Message = "asd1", MessageId = 2},
new Conversation() {Id = 1, Message = "asd2", MessageId = 3},
new Conversation() {Id = 2, Message = "asd3", MessageId = 4},
new Conversation() {Id = 2, Message = "asd4", MessageId = 5},
new Conversation() {Id = 2, Message = "asd5", MessageId = 6},
new Conversation() {Id = 3, Message = "asd6", MessageId = 7}
};
var outputList = inputList.GroupBy(x => x.Id)
.SelectMany(x => x.OrderBy(y => y.MessageId).Take(2).ToList())
.ToList();
Console.ReadKey();
}
}
I have a C# List of following fields which are returned by a stored procedure:
CarrierId ParentCarrierId Name Descrition
1 NULL A AA
2 1 B BB
3 1 C CC
4 3 D DD
5 NULL E EE
I need to construct a nested object list out of this output
So each object of Carrier should have list of all it's children. Can anyone help me construct a LINQ code to accomplish this?
Desired Result:
CarrierId = 1
|__________________ CarrierId = 2
|
|__________________ CarrierId = 3
|
| |___________________ CarrierId = 4
CarrierId = 5
Desired result should be as mentioned above
the following code arrange things in tree but a child still appears in the list
c.Children = carrierList.Where(child => child.ParentCarrierId == c.CarrierId).ToList();
CarrierId = 1
|
|__________________ CarrierId = 2
|
|__________________ CarrierId = 3
| |___________________ CarrierId = 4
|
CarrierId = 2
|
CarrierId = 3
|
CarrierId = 4
|
CarrierId = 5
I don't want this behavior. If something appeared as Child it should be removed from root.
This is what you need.
First, start with the source data:
var source = new []
{
new { CarrierId = 1, ParentCarrierId = (int?)null, Name = "A", Description = "AA", },
new { CarrierId = 2, ParentCarrierId = (int?)1, Name = "B", Description = "BB", },
new { CarrierId = 3, ParentCarrierId = (int?)1, Name = "C", Description = "CC", },
new { CarrierId = 4, ParentCarrierId = (int?)3, Name = "D", Description = "DD", },
new { CarrierId = 5, ParentCarrierId = (int?)null, Name = "E", Description = "EE", },
};
Then, create a lookup by ParentCarrierId:
var lookup = source.ToLookup(x => x.ParentCarrierId);
Now we need an output structure:
public class Carrier
{
public int Id;
public List<Carrier> Children = new List<Carrier>();
public string Name;
public string Description;
}
Then, a build function to pop all of the carriers out by ParentCarrierId:
Func<int?, List<Carrier>> build = null;
build = pid =>
lookup[pid]
.Select(x => new Carrier()
{
Id = x.CarrierId,
Name = x.Name,
Description = x.Description,
Children = build(x.CarrierId),
})
.ToList();
NB: It's recursive so it needs to be defined with the initial = null.
Finally we build:
List<Carrier> trees = build(null);
This gives:
Let's say I have three tables. Basically, one table of things, one table describing the possible attributes of the things, and a bridge table providing the values for those attributes for a particular thing. This lets you do dynamic metadata.
Users can add metadata properties at any time, and then they can provide values for those properties for any thing in the "things" table.
So like this:
Table: Persons
PersonID | FirstName | LastName
1 | Bob | Jones
2 | Fred | Smith
3 | Sally | Doe
Table: Properties
PropertyID | Name
1 | SupervisorName
2 | Age
3 | Birthday
4 | EmployeeNumber
5 | Hometown
Table: PropertyValues
PersonID | PropertyID | PropertyValue
1 | 1 | Frank Grimes
1 | 2 | 47
2 | 2 | 35
2 | 4 | 1983738
2 | 3 | 5/5/1978
3 | 3 | 4/4/1937
3 | 5 | Chicago, IL
So, users want to be able to view a report of these properties. Maybe I want to see a table containing the age and birthday of all employees. There would be blanks in the table if those values aren't populated for those users. Then maybe I want to see a report that includes supervisor, age and birthday--I should be able to generate that table on the fly as well.
Now, in order to do this with SQL, I would dynamically construct a query and add a join to that query for each property that I want to pivot up to the top. That's how it works now.
If I wanted to do this in LINQ, and I knew which properties to pivot while writing the code, I could do that too--I'd just use GroupJoin().
What I don't know is how to dynamically construct a LINQ query that would allow me to pivot any number of properties at runtime, without knowing what they are ahead of time.
Any ideas?
(Before you knee-jerk mark this as a duplicate, know that I did a fair amount of StackOverflow research before posting this question, and if this exact question has been asked before, I couldn't find it.)
You can build linq expression trees dynamically. This topic is covered (including example) in the following MSDN article: http://msdn.microsoft.com/en-us/library/bb882637.aspx
My suggestion would be to write an example Linq query for your task and rebuild it programmatically with expression trees. Once it works you adapt it and inject your dynamic parts.
My thought is you could join with a where condition as follows:
Person Class
public class Person
{
public int Id {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
}
Property Class
public class Property
{
public int Id {get; set;}
public string Name {get; set;}
}
Value Class
public class Value
{
public int PersonId {get; set;}
public int PropertyId {get; set;}
public string Val {get; set;}
}
Code
void Main()
{
var selectBy = "Birthday";
var persons = new List<Person>() { new Person {Id = 1, FirstName = "Bob", LastName = "Jones"}, new Person {Id = 2, FirstName = "Fred", LastName = "Smith"}, new Person {Id = 3,FirstName = "Sally", LastName = "Doe"}};
var properties = new List<Property>() { new Property {Id = 1, Name = "SupervisorName"}, new Property {Id = 2, Name = "Age"}, new Property {Id = 3, Name = "Birthday"}, new Property {Id = 4, Name = "EmployeeNumber"}, new Property {Id = 5, Name = "Hometown"}};
var values = new List<Value>() { new Value {PersonId = 1, PropertyId = 1, Val="Frank Grimes"}, new Value {PersonId = 1, PropertyId = 2, Val="47"}, new Value {PersonId = 2, PropertyId = 2, Val="35"}, new Value {PersonId = 2, PropertyId = 4, Val="1983738"}, new Value {PersonId = 2, PropertyId = 3, Val="5/5/1978"}, new Value {PersonId = 3, PropertyId = 3, Val="4/4/1937"}, new Value {PersonId = 3, PropertyId = 5, Val="Chicago, IL"}};
var result = from v in values
join p in persons on v.PersonId equals p.Id
join p2 in properties on v.PropertyId equals p2.Id
where p2.Name.Equals(selectBy)
select new { Name = p.FirstName + " " + p.LastName,
Value = v.Val
};
result.Dump();
}
Results
Name, Value
Fred Smith 5/5/1978
Sally Doe 4/4/1937
Revised Answer
void Main()
{
var selectBy = "Birthday";
var persons = new List<Person>() { new Person {Id = 1, FirstName = "Bob", LastName = "Jones"}, new Person {Id = 2, FirstName = "Fred", LastName = "Smith"}, new Person {Id = 3,FirstName = "Sally", LastName = "Doe"}};
var properties = new List<Property>() { new Property {Id = 1, Name = "SupervisorName"}, new Property {Id = 2, Name = "Age"}, new Property {Id = 3, Name = "Birthday"}, new Property {Id = 4, Name = "EmployeeNumber"}, new Property {Id = 5, Name = "Hometown"}};
var values = new List<Value>() { new Value {PersonId = 1, PropertyId = 1, Val="Frank Grimes"}, new Value {PersonId = 1, PropertyId = 2, Val="47"}, new Value {PersonId = 2, PropertyId = 2, Val="35"}, new Value {PersonId = 2, PropertyId = 4, Val="1983738"}, new Value {PersonId = 2, PropertyId = 3, Val="5/5/1978"}, new Value {PersonId = 3, PropertyId = 3, Val="4/4/1937"}, new Value {PersonId = 3, PropertyId = 5, Val="Chicago, IL"}};
// Default Values for the Cartesian Product
var defaultValues = new string[]{"","","","",""};
// propertyKeys are used to filter values generated for pivot table
var propertyKeys = new List<Property> { new Property{Id=1}, new Property{Id=2}, new Property{Id=3}};
// Generate default values for every person and each property
var cartesianProduct = from ppl in persons
from prop in properties
join pk in propertyKeys on prop.Id equals pk.Id
select new {PersonId = ppl.Id, PropertyId = prop.Id, Val = defaultValues[prop.Id-1]};
// Create Pivot Values based on selected PropertyIds
var newValues = from cp in cartesianProduct
join v in values on new {cp.PersonId, cp.PropertyId} equals new { v.PersonId, v.PropertyId } into gj
from x in gj.DefaultIfEmpty()
select new {
PersonId = (x == null ? cp.PersonId : x.PersonId),
PropertyId = (x == null ? cp.PropertyId: x.PropertyId),
Val = ( x == null ? cp.Val : x.Val )
};
foreach( var y in newValues )
{
var aPerson = persons.Where( r=> r.Id == y.PersonId ).First().FirstName;
var aProperty = properties.Where( r=> r.Id == y.PropertyId ).First().Name;
Console.WriteLine(string.Format("{0:12} {1:12} {2:12}", aPerson, aProperty, y.Val));
}
}
Results:
Bob | SupervisorName | Frank Grimes
Bob | Age | 47
Bob | Birthday |
Fred | SupervisorName |
Fred | Age | 35
Fred | Birthday | 5/5/1978
Sally | SupervisorName |
Sally | Age |
Sally | Birthday | 4/4/1937
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I want to retrieve all records of manufacturer in LINQ using a many-to-many join.
Lets consider
Table 'Item':
id | name
----------
1 | A
---------
2 | B
--------
3 | C
--------
Table 'Manufacturer':
ManufactuerId | Name
-------------------
1 | XYZ
-------------------
2 | ABC
--------------------
3 | ZZZ
----------------------
Table 'ManufacturerItem':
ManufacturerItemID | ManufacturerId | ItemId
-------------------------------------------
1 | 1 | 1
-------------------------------------------
2 | 1 | 2
-------------------------------------------
3 | 2 | 1
------------------------------------------
4 | 3 | 2
------------------------------------------
5 | 1 | 3
------------------------------------------
I would like to fetch the records of a manufacturer(s) who has all the items that I have supplied in.
So, given an input of {A, B} I would like to get an output of XYZ Manufacturer.
var tableItems = new[] {
new { Id = 1, Name = "A" },
new { Id = 2, Name = "B" },
new { Id = 3, Name = "C" }
};
var tableManufacturer = new[] {
new { ManufacturerId = 1, Name = "XYZ" },
new { ManufacturerId = 2, Name = "ABC" },
new { ManufacturerId = 3, Name = "ZZZ" }
};
var tableManufacturerItem = new[]
{
new {ManufacturerItemID = 1, ManufacturerId = 1, ItemId = 1},
new {ManufacturerItemID = 2, ManufacturerId = 1, ItemId = 2},
new {ManufacturerItemID = 3, ManufacturerId = 2, ItemId = 1},
new {ManufacturerItemID = 4, ManufacturerId = 3, ItemId = 2},
new {ManufacturerItemID = 5, ManufacturerId = 1, ItemId = 3},
};
var itemsToSearch = new[] { "A", "B" };
var result = tableManufacturerItem
.GroupBy(x => x.ManufacturerId)
.Where(m => tableItems.Where(item => itemsToSearch.Contains(item.Name)).Select(x => x.Id)
.Except(m.Select(x => x.ItemId))
.Count() == 0)
.Select(x => tableManufacturer.First(m => m.ManufacturerId == x.Key))
.Select(m => m.Name)
.ToList();