简体   繁体   中英

LINQ Query to join 2 different tables

I am currently working on library management project, which users update their reading status when they borrow a book. When I use linq to join tables, everything else except status is there. Here are my models: User model(TblUser):

using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;

namespace LibMan.Models.DB
{
    public partial class TblBookStatus
    {
        public int ResId { get; set; }
        public string UserEmail { get; set; }
        public int? BookId { get; set; }
        public string Status { get; set; }

        public virtual TblBook Book { get; set; }
        public virtual TblUser User { get; set; }
    }

}

Book Model(TblBook):

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace LibMan.Models.DB
{
    public partial class TblBook
    {
        public int BookId { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public string Translator { get; set; }
        public string Publisher { get; set; }
        public string Description { get; set; }
        public string Category { get; set; }
        public byte[] Cover { get; set; }
    }
}

Book Status Model(TblBookStatus):

using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;

namespace LibMan.Models.DB
{
    public partial class TblBookStatus
    {
        public int ResId { get; set; }
        public string UserEmail { get; set; }
        public int? BookId { get; set; }
        public string Status { get; set; }

        public virtual TblBook Book { get; set; }
        public virtual TblUser User { get; set; }
    }

}

And moving on to my code below, you can see I created DisplayBook with linq query join, but I can't display status in html side. It says it

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LibMan.Models.DB;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;

namespace LibMan.Pages
{
    public class LibraryModel : PageModel
    {
        private readonly LibMan.Models.DB.LibManContext _context;
        public string Message { get; set; }
        public LibraryModel(LibMan.Models.DB.LibManContext context)
        {
            _context = context;
            DisplayBook = new List<TblBook>();
;       }
        public IList<TblBookStatus> TblBookStatus { get; set; }
        public IList<TblBook> TblBook { get; set; }
        public IList<TblBook> DisplayBook { get; set; }
        public IList<TblUser> TblUser{ get; set; }
        [BindProperty]
        public async Task OnGetAsync()
        {
            TblBookStatus = await _context.TblBookStatus.ToListAsync();
            TblBook = await _context.TblBook.ToListAsync();
            TblUser = await _context.TblUser.ToListAsync();
            var UserEmail = HttpContext.Session.GetString("Email");

            if (TblBookStatus != null && TblBook != null)
            {
                DisplayBook = (from book in TblBook
                                join stat in TblBookStatus
                                on book.BookId equals stat.BookId
                                join usr in TblUser
                                on stat.UserEmail equals usr.Email
                                where (usr.Email == UserEmail)
                                select book).ToList();
            }
        }
    }
}

How can I do this? Can I do this inside the statement that DisplayBook is assigned to the Linq query result? What is the best way to do this? Thanks: Note. I can provide html code if needed.

There are multiple options. You can correct your code changing type of DisplayBook to valuetuple (or create separate type for readability):

 public IList<(TblBook Book, TblBookStatus  Status)> DisplayBook { get; set; }

Fetch books and statuses with users:

TblBookStatus = await _context.TblBookStatus.Include(s => s.User).ToListAsync();
TblBook = await _context.TblBook.ToListAsync();

And lastly fill the DisplayBook :

DisplayBook = TblBook
    .Select(b => (b, TblBookStatus.FirstOrDefault(s => s.BookId == b.BookId)))
    .ToList()

As far as I understand from your comments you have one-to-one relation between status and user, so this way you will have all books in your library fetched and all statuses(status for book can be null ) with filled user relation.

PS

ToListAsync should not return null, so null checks are redundant there.

UPD

To fetch info only about one user and his books you can do it with single query:

var statusesWithUserAndBooks = await _context.TblBookStatus
    .Include(s => s.User)
    .Include(s => s.Book)
    .Where(s => s.User.Email == UserEmail)
    .ToListAsync();

If you need a subset of data, then create DTO with necessary fields, like this:

public class DisplayBookDTO
{
    public string Status { get; set; }
    public string BookTitle { get; set; }
    public string BookAuthor { get; set; }
}

public IList<DisplayBookDTO> DisplayBook { get; set; }

DisplayBook = await _context.TblBookStatus
    .Where(s => s.User.Email == UserEmail
        && s.Book != null)
    .Select(s => new DisplayBookDTO
    {
        Status = s.Status,
        BookTitle = s.Book.Title,
        BookAuthor = s.Book.Author
    })
    .ToListAsync();

No need to query each table separately, and then join. Let LINQ do it for you.

var UserEmail = HttpContext.Session.GetString("Email");

var books = _context.TblBookStatus
     .Where(
        tbs => (tbs.Book != null && tbs.BookId == tbs.Book.BookId) &&
               (tbs.User != null && tbs.UserEmail == tbs.User.Email) &&
               tbs.UserEmail == UserEmail)
     .Select(tbs => tbs.Book);

Because you have navigation properties defined in the EF models (using virtual properties), the joins will automatically be done for you. In the above example, I am setting tbs.BookId == tbs.Book.BookId. This does not need to be done if you have foreign key defined in entity type configurations. You can alternatively define these relationships using attributes in your EF models.

Example:

[ForeignKey("BookId")]
public virtual TblBook Book { get; set; }

EDIT: Break down what you need to return:

var books = _context.TblBookStatus
         .Where(
            tbs => (tbs.Book != null && tbs.BookId == tbs.Book.BookId) &&
                   (tbs.User != null && tbs.UserEmail == tbs.User.Email) &&
                   tbs.UserEmail == UserEmail)
         .Select(tbs => new {
                   Status = tbs.Status,
                   BookName = tbs.Book.Name
                             });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM