Loading nested properties in Entity Framework Core - c#

Is there any way I can avoid using Include and ThenInclude in EF Core ?
I have these models and dtos :
For Book:
public partial class Book
{
public Book()
{
BookAuthors = new HashSet<BookAuthor>();
BookCategories = new HashSet<BookCategory>();
Reviews = new HashSet<Review>();
}
public int BookId { get; set; }
public string Title { get; set; }
...
public string ImageUrl { get; set; }
public ICollection<BookAuthor> BookAuthors { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
public ICollection<Review> Reviews { get; set; }
}
public class BookDto
{
public int BookId { get; set; }
public string Title { get; set; }
...
public string ImageUrl { get; set; }
public IList<AuthorDto> Authors { get; set; }
public IList<CategoryDto> Categories { get; set; }
public IList<ReviewDto> Reviews { get; set; }
}
For Author :
public partial class Author
{
public Author()
{
BookAuthors = new HashSet<BookAuthor>();
}
public int AuthorId { get; set; }
public string AuthorName { get; set; }
...
public ICollection<BookAuthor> BookAuthors { get; set; }
}
public class AuthorDto
{
public int AuthorId { get; set; }
public string AuthorName { get; set; }
...
public IList<BookDto> Books { get; set; }
}
For Category:
public partial class Category
{
public Category()
{
BookCategories = new HashSet<BookCategory>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class CategoryDto
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public IList<BookDto> Books { get; set; }
}
And Review :
public partial class Review
{
public int ReviewId { get; set; }
public int BookId { get; set; }
public int UserId { get; set; }
public DateTime? Date { get; set; }
public string Comments { get; set; }
public decimal? Rating { get; set; }
public Book Book { get; set; }
public User User { get; set; }
}
public class ReviewDto
{
public int ReviewId { get; set; }
public int BookId { get; set; }
public int UserId { get; set; }
public DateTime? Date { get; set; }
public string Comments { get; set; }
public decimal? Rating { get; set; }
public Book Book { get; set; }
public User User { get; set; }
}
I have this :
public IEnumerable<Book> GetAll()
{
var books = _context.Book
.Include(e => e.BookAuthors)
.ThenInclude(a => a.Author)
.Include(c => c.BookCategories)
.ThenInclude(categ => categ.Category)
.Include(r => r.Reviews)
.AsNoTracking()
.ToList();
return books;
}
And then in Author :
public IEnumerable<Author> GetAll()
{
var authors = _context.Author
.Include(e => e.BookAuthors)
.ThenInclude(b => b.Book)
.ToList();
return authors;
}
public Author GetById(int id)
{
return _context.Author.Include("BookAuthors.Book").SingleOrDefault(x =>
x.AuthorId == id);
}
Between Books and Authors, Books and Categories I have many to many relationship, between Review and Books one to many relationship.
I need this because on the list with books I display the name of the author as well, on an author detail page I display his books and so on. I'm using AutoMapper and DTOs as well.
The same for Categories, Reviews..my json with the returned data becomes very big and it takes a lot of time to load the data into the page, because it has this nested structure. What would be the best solution to do this ?

There's a way to do Eager loading. I tried by GroupJoin(expression).SelectMany(...).
This will allow you to load till one level avoiding circular rerefence. I'll show you how I archived it, but with your models.
You have:
var books = _context.Book
.Include(e => e.BookAuthors)
.ThenInclude(a => a.Author)
.Include(c => c.BookCategories)
.ThenInclude(categ => categ.Category)
.Include(r => r.Reviews)
.AsNoTracking()
.ToList();
return books;
By the way, you dont put BookAuthors model. So, I'll assume it's structure:
var books = _context.Authors
.GroupJoin(_context.Book,
t1 => new { IdAuthor = (Guid)t1.Id }, //where t1 = Authors, you should have IdAuthor in Book.
a => new { IdAuthor = (Guid)a.IdAuthor }, //where a = Book
(t1, a_join) => new { t1, a_join })
.SelectMany(t1 => t1.a_join.DefaultIfEmpty(), (t1, a) => new { t1, a }) //.DefaultIfEmpty() = LEFT JOIN
.Select(x => new AuthorBooksDTO
{
IdAutor = t1.t1.Id //please navegate t1 till VS shoows you which model is
Books = t1.t1.a_join.toList() //navegate t1. a_join will be the list of books.
....
})
.ToList();
For sure, it takes more time to build but performance improve incredibly.
Let us know if it works for you.

Related

Linq Group by relative entites

My scenario: Users will be able to create lists and add items to these lists. What I want to do is to find the items in the lists created by the users at most.
Item Entity
public class Item:BaseEntity
{
public string Name { get; set; }
public decimal Price { get; set; }
public decimal DiscountedPrice{ get; set; }
public virtual ICollection<ItemList> ItemLists { get; set; }
}
Item List Entity
public class ItemList:BaseEntity
{
public string Name { get; set; }
public string Description { get; set; }
public int UserId { get; set; }
public ICollection<Item> Items { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
}
User Entity
public class User:BaseEntity
{
public string Name { get; set; }
public string Surname { get; set; }
public string Gsm { get; set; }
public string Email { get; set; }
public virtual ICollection<ItemList> ItemLists{ get; set; }
}
my DTO
public class TopItemsForUsers
{
[BsonRepresentation(BsonType.ObjectId)]
[BsonId]
public string ItemId { get; set; }
public string UserId { get; set; }
public int Quantity { get; set; }
}
My Item repository
var query = _context.Items.Include(l => l.ItemLists)
.GroupBy(g => g.ItemLists)
.Select(z => new TopItemsInLists { ItemId = z.Key.ToString(), Quantity = z.Count() })
.OrderByDescending(z => z.Quantity)
.Take(10);
I want to get products that are very present in users' lists
Where am I doing wrong? If anyone has any other suggestions
Try this query. I hope I understand question correctly.
var query =
from u in _context.Users
from il in u.ItemLists
from i in il.Items
group i by new { UserId = u.Id, ItemId = i.Id } into g
select new TopItemsInLists
{
UserId = g.Key.UserId.ToString(),
ItemId = g.Key.ItemId.ToString(),
Quantity = g.Count()
};
query = query
.OrderByDescending(z => z.Quantity)
.Take(10);

How can i merge two object both Class and Generic List<T> use LINQ

How can i merge two object both Class and Generic List according to followed example?
public class BookShop
{
public Author Author { get; set; }
public List<Book> Book { get; set; }
}
public class Author
{
public string ID { get; set; }
public string Name { get; set; }
}
public class Book
{
public int ID { get; set; }
public string Name { get; set; }
}
public class BookDetail
{
public int ID { get; set; }
public string Comment { get; set; }
public int BookID { get; set; }
}
List<BookShop> shop = db.GetBook();
List<BookDetail> bookDetail = db.GetBookDetail();
var merge = shop.Select(m => m.Book.Join(bookDetail, ok => ok.ID, ik => ik.BookID, (b, d) => new
{
b.ID,
b.Name,
d.Comment
}));
So far so everything is okay, but i need to Author detail (Author.Name)
It seems like you're using entity framework. Your structure should look more like this:
public class Author
{
public string ID { get; set; }
public string Name { get; set; }
public virtual IEnumerable<Book> Books { get; set; }
}
public class Book
{
public int ID { get; set; }
public string Name { get; set; }
public int AuthorID { get; set; }
public Author Author { get; set; }
public IEnumerable<BookDetail> Details { get; set; }
}
public class BookDetail
{
public int ID { get; set; }
public string Comment { get; set; }
public int BookID { get; set; }
public Book Book { get; set; }
}
This lets you have a direct connection between books, authors, and book details. Then in Linq you can just do something like this to get details on a book
Books.Select(x => new
{
ID = x.ID,
Name = x.Name,
Comments = x.Details.Select(d => d.Comment)
Author = x.Author
})

SQL to IQueryable LINQ

Hello I am trying to convert the following SQL statement into its LINQ equivalent and since I am really new to .net (coding for one day) i have gotten stuck on this for hours now.
SELECT *
FROM Books
WHERE BookID IN (SELECT BookID
FROM Borrows
WHERE UserID = 2)
This is the model
public class LibUser
{
[Key]
public int UserID { get; set; }
[Required, StringLength(50), Display(Name = "First Name")]
public string UserFirstName { get; set; }
[Required, StringLength(50), Display(Name = "Last Name")]
public string UserLastName { get; set; }
[Required, StringLength(10000), Display(Name = "Residence"), DataType(DataType.MultilineText)]
public string Adress { get; set; }
}
public class Book {
[Key]
public int BookID { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public DateTime Published{ get; set; }
}
public class Borrowed {
[Key]
public int BorrowID { get; set; }
public int UserID { get; set; }
public int BookID { get; set; }
}
I would greatly appreciate anyones help.
EDIT
Context class
public class LibraryContext : DbContext
{
public LibraryContext()
: base("libromatic")
{
}
public DbSet<LibUser> LibUsers { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<Borrowed> Borrows { get; set; }
}
Assuming your context is called db, you could do the following query
var borrowedBooksForUser = db.Books
.Where(b => db.Borrowed.Any(x => x.UserID == 2 && x.BookID == b.BookID));
It might be preferable to do this with a join.
The argument is: If a user borrows huge amounts of books, or there is an error in the data, then your subquery could return a lot of IDs, and SQL 'IN' clauses on long lists can get really slow.
Using a join:
SQL query:
SELECT Books.* FROM Books
JOIN Borrows ON Borrows.BookID = Books.BookID
WHERE Borrows.UserID = 2
Linq statement:
var allBooksBorrowedByUser2 = db.Borrowed
.Where(borrow => borrow.UserID == 2)
.Join(db.Books,
borrow => borrow.BookID,
book => book.BookID,
(borrow, book) => book);
Navigation would make everything more simple.
public class Borrowed {
[Key]
public int BorrowID { get; set; }
public int UserID { get; set; }
public int BookID { get; set; }
// Navigation Properties
public virtual LibUser User { get; set; }
public virtual Book Book { get; set; }
}
Borrows.Where(borrow => borrow.UserId == 2)
.Select(borrow => borrow.Book);
You Could Do something Like This:
var Lnq = new LinqDataContext();
var borrowId = Lnq.Borrowed.Where(a => a.UserID == 2).Select(a => a.BookID).ToList();
var bookQuery = Lnq.Books.Where(a => borrowId.Contains(a.BookID))
.Select(a => a.YourColumn);
try this,
var getResult=from b in db.Books
join bo in db.Borrows on b.BookID=bo.BookID
where bo.UserID=2

How can I include down more than one level in a LINQ expression?

I have the following classes:
public partial class Exam
{
public Exam()
{
this.Objectives = new List<Objective>();
}
public int ExamId { get; set; }
public string Name { get; set; }
public virtual ICollection<Objective> Objectives { get; set; }
}
public partial class Objective
public Objective()
{
this.ObjectiveDetails = new List<ObjectiveDetail>();
}
public int ObjectiveId { get; set; }
public int ExamId { get; set; }
public string Text { get; set; }
public virtual Exam Exam { get; set; }
public virtual ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
}
public partial class ObjectiveDetail
{
public ObjectiveDetail()
{
this.SubTopics = new List<SubTopic>();
}
public int ObjectiveDetailId { get; set; }
public int ObjectiveId { get; set; }
public int Number { get; set; }
public string Text { get; set; }
public virtual Objective Objective { get; set; }
}
I created the following to get the Exam and Objective data:
var exam = _examsRepository.GetAll()
.Where(e => e.Name == name)
.Include(e => e.Objectives)
.FirstOrDefault();
But how can I also include the ObjectiveDetails ?
Like this:
var exam = _examsRepository.GetAll()
.Where(e => e.Name == name)
.Include(e => e.Objectives)
.Include(e => e.Objectives.Select(o => o. ObjectiveDetails))
.FirstOrDefault();
MSDN article on loading related entities.

How can I create a LINQ expression that joins four tables and allows a where on the topmost table?

I have the following classes:
public partial class Topic
{
public Topic()
{
this.SubTopics = new List<SubTopic>();
}
public int TopicId { get; set; }
public string Name { get; set; }
public int SubjectId { get; set; }
public virtual Subject Subject { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public partial class SubTopic
{
public SubTopic()
{
this.Problems = new List<Problem>();
}
public int SubTopicId { get; set; }
public int TopicId { get; set; }
public string Name { get; set; }
public virtual ICollection<Problem> Problems { get; set; }
public virtual Topic Topic { get; set; }
}
public class Problem
{
public Problem()
{
this.Questions = new List<Question>();
}
public int ProblemId { get; set; }
public int SubTopicId { get; set; }
public string Title { get; set; }
public virtual SubTopic SubTopic { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class Question
{
public int QuestionId { get; set; }
public int ProblemId { get; set; }
public string Text { get; set; }
public virtual Problem Problem { get; set; }
}
Can someone help tell me how I can construct a LINQ expression. What I need to do is to get just the QuestionId of the Questions that have a SubjectId = 0.
Here is a LINQ I created to get questions where the ProblemId was given:
var questions = _questionsRepository
.GetAll()
.Where(a => a.ProblemId == problemId)
.ToList();
For this could someone tell me how I can make the LINQ expression join all the above tables so I can enter a SubjectId = 0;
You have all the navigation properties needed, so you can just do :
var questions = _questionsRepository.GetAll()
.Where(m => m.Problem.SubTopic.Topic.SubjectId == 0)
.Select(m => m.QuestionId);
you may need some null check
.Where(m => m.Problem != null &&
m.Problem.SubTopic ! null &&
m.Problem.SubTopic.Topic != null &&
m.Problem.SubTopic.Topic.SubjectId == 0)
.Select(m => m.QuestionId);

Categories