如何有效地使用Entity Framework Core?

c# entity-framework entity-framework-core

我們來看看簡單的類示例:

public class Book
{
    [Key]
    public string BookId { get; set; }
    public List<BookPage> Pages { get; set; }
    public string Text { get; set; }
} 

public class BookPage
{
    [Key]
    public string BookPageId { get; set; }
    public PageTitle PageTitle { get; set; }
    public int Number { get; set; }
}

public class PageTitle
{
    [Key]
    public string PageTitleId { get; set; }
    public string Title { get; set; }
}

所以,如果我想獲得所有PageTitiles,如果我只知道BookId,我需要寫一些包含,如下所示:

using (var dbContext = new BookContext())
{
    var bookPages = dbContext
    .Book
    .Include(x => x.Pages)
    .ThenInclude(x => x.PageTitle)//.ThenInclude(x => x.Select(y => y.PageTitle)) Shouldn't use in EF Core
    .SingleOrDefault(x => x.BookId == "some example id")
    .Pages
    .Select(x => x.PageTitle);
}

如果我想讓PageTitles與其他書籍連接,我需要再次重寫此方法,除了BookId之外沒有任何改變!這是使用數據庫的非常低效的方法,在這個例子中我有3個類,但是如果我有數百個類,嵌套到非常深的層次,那麼工作會非常緩慢和不舒服。

我應該如何組織使用我的數據庫,以避免許多包含和冗餘查詢?

一般承認的答案

問題1:我每次都要添加一堆Includes

好吧,由於您必須在EF中明確包含相關數據,因此無法解決這個問題,但您可以輕鬆創建擴展方法以使其更清晰:

public static IQueryable<Book> GetBooksAndPages(this BookContext db)
{
    return db.Book.Include(x => x.Pages);
}

public static IQueryable<Book> GetBooksAndPagesAndTitles(this BookContext db)
{
    return GetBooksAndPages(db).ThenInclude(p => p.PageTitle)

}

然後你可以這樣做:

var bookPages = dbContext
    .GetBooksAndPagesAndTitles()
    .SingleOrDefault(x => x.BookId == "some example id")
    .Pages
    .Select(x => x.PageTitle);

問題2:我必須多次為不同的ID編寫此查詢。

為什麼不將它重構為帶有bookId參數的方法?

public IEnumerable<PageTitle> GetPageTitlesForBook(BookContext dbContext, int bookId)
{
    return dbContext
        .GetBooksAndPagesAndTitles()
        .SingleOrDefault(x => x.BookId == bookId)
        .Pages
        .Select(x => x.PageTitle);
}

底線 - 如果您發現自己多次編寫相同的內容,那麼這是將代碼重構為可重複使用的較小方法的絕佳機會。


熱門答案

給出的示例都不需要任何Include語句。如果您在查詢結尾處使用select並且仍在使用諸如DbSet之類的IQueryable,則Entity Framework將執行所謂的“投影”,並將運行包含所有必需字段的查詢自動。

例如,您的原始代碼:

using (var dbContext = new BookContext())
{
    var bookPages = dbContext
        .Book
        .Include(x => x.Pages)
        .ThenInclude(x => x.PageTitle)//.ThenInclude(x => x.Select(y => y.PageTitle)) Shouldn't use in EF Core
        .SingleOrDefault(x => x.BookId == "some example id")
        .Pages
        .Select(x => x.PageTitle);
}

您可以這樣重寫:

using (var dbContext = new BookContext())
{
    var bookPages = dbContext
        .Book
        .Where(x => x.BookId == "some example id")
        .SelectMany(x => x.Pages.Select(y => y.PageTitle))
        .ToList();
}

以下是實體框架將解決此問題的方法:

  1. 我們告訴Entity Framework我們將查看books表中的條目
  2. 然後我們告訴實體框架我們只想要具有特定ID的書籍(當然應該只是一個記錄)
  3. 從那裡,我們告訴實體框架我們想要所有書籍頁面的列表(再次,由於Where語句,這將只是一本書的頁面)
  4. 然後我們告訴Entity Framework我們只需要每頁的PageTitle
  5. 最後,我們告訴實體框架使用我們剛剛提供的所有信息來生成查詢並執行它

如果您想了解實體框架如何執行它所做的事情,最後一步是至關重要的一步。在您調用SingleOrDefault示例中,您正在指示實體框架執行查詢,這就是您需要包含的原因。在您的示例中,您實際上沒有告訴Entity Framework在運行查詢時需要這些頁面,因此您必須使用Include手動請求它們。

在我發布的示例中,您可以看到,當您運行查詢時( ToList是觸發查詢執行的內容),Entity Framework從您的Select表達式中知道它將需要頁面及其標題。更好 - 這意味著實體框架甚至不會在它生成的SELECT語句中包含未使用的列。

我強烈建議調查預測,它們可能是我知道的最好的方法,以消除連續手動包含東西的要求。



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow