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 |
Advertisement
Answer
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 }
}