【问题标题】:LINQ Query to join 2 different tablesLINQ Query 加入 2 个不同的表
【发布时间】:2020-05-30 19:45:47
【问题描述】:

我目前正在从事图书馆管理项目,用户在借书时会更新他们的阅读状态。当我使用 linq 连接表时,除了状态之外的所有其他内容都在那里。 这是我的模型: 用户模型(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; }
    }

}

图书模型(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; }
    }
}

图书状态模型(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; }
    }

}

继续下面的代码,您可以看到我使用 linq 查询连接创建了 DisplayBook,但我无法在 html 端显示状态。它说它

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();
            }
        }
    }
}

我该怎么做?我可以在将 DisplayBook 分配给 Linq 查询结果的语句中执行此操作吗?做这个的最好方式是什么?谢谢! 注意:如果需要,我可以提供 html 代码。

【问题讨论】:

  • TblBookStatus 具有可为空的BookId,为什么会这样?因为看起来这张表只是书籍和用户之间的多对多。将public string Status { get; set; } 移动到单独的表并TblBookStatus 引用它会更有意义。
  • 你想用DisplayBook 显示什么?
  • TblBookStatus 上的 BookId 作为数据库上的外键进行管理,这就是它可以为空的原因,它是在我设置项目时自动生成的。在 displaybook 声明中,当用户将一本书添加到他们的图书馆时,我会在该页面上显示它,我会显示图书信息。我还想显示不在 DisplayBook 中的 TblBookStatus 的状态。一位用户拥有一本书。正如你所说,这个项目适用于许多用户和书籍。
  • 一个用户只能拥有一本书?
  • 你能添加你的User类吗,TblBookStatus重复了两次。

标签: c# html linq razor


【解决方案1】:

有多种选择。您可以将代码更改类型 DisplayBook 更正为 valuetuple(或创建单独的类型以提高可读性):

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

与用户一起获取书籍和状态:

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

最后填写DisplayBook:

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

据我从您的 cmets 了解到,您在状态和用户之间存在一对一的关系,因此您将获取图书馆中的所有书籍和所有状态(书籍的状态可以是 null)填充用户关系。

附言

ToListAsync 不应返回空值,因此空值检查在那里是多余的。

UPD

要仅获取有关一位用户及其书籍的信息,您可以通过单个查询来完成:

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

如果您需要数据子集,则使用必要的字段创建DTO,如下所示:

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();

【讨论】:

  • 嗨,我已经尝试过了,我终于可以看到状态为输出,但是当 DisplayBook 显示时,数据库中的所有书籍(TblBook 上的每一本书)都显示出来了。现在正试图解决这个问题。有什么想法吗?
  • 图书馆页面包含用户自己的图书馆,他们从 TblBook 添加书籍。由于一个用户可以拥有不止一本书并且他们不止一个用户,因此必须显示用户的状态和用户预订的图书,而不应显示所有图书和状态。
  • @Fuzzyy 很乐意提供帮助!
【解决方案2】:

无需单独查询每个表,然后加入。让 LINQ 为您完成。

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);

因为您在 EF 模型中定义了导航属性(使用虚拟属性),所以会自动为您完成连接。在上面的例子中,我设置了 tbs.BookId == tbs.Book.BookId。如果您在实体类型配置中定义了外键,则不需要这样做。您也可以使用 EF 模型中的属性来定义这些关系。

例子:

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

编辑:分解您需要返回的内容:

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
                             });

【讨论】:

  • 哦,我不知道。那么如何显示 var 书籍,我之前有一个列表 DisplayBook 用于在 html 上显示。
  • 您可以在末尾添加 .ToList() 以使其成为列表。 Select(tbs => tbs.Book).ToList();
  • 我仍然无法显示来自 TblBookStatus 的状态,我知道 DisplayBook 是 TblBook 的一种,但是当我尝试在表格的 html 端打印它时,我不能。我可以给你提供html代码。
  • 在上面查看我的编辑。如果您添加了导航属性(我在原始答案中提到的虚拟属性),则可以展平返回结果。在进行此更改后放置一个断点,并查看是否在返回客户端 (html) 之前获得所需的结果。如果您愿意,可以创建一个新的 StackOverflow 问题并在那里发布您的客户端代码。然后我们可以专注于该部分。
  • 另外,想提一下,如果您不在 WHERE 子句或 SELECT 中使用导航属性,则根本不会进行连接。最好在选择中明确定义所有要返回的属性。
猜你喜欢
  • 2012-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多