[英]How to do multiple join with group-by in LINQ
我想在一行中顯示一本書的類別。 我可以在 LINQPad 6 應用程序中執行此操作,但是當我通過 C# 執行此操作時出現錯誤,我不明白我的錯誤。
Table Books Categories Books_Categories
BookId | Name CategoryId | Name Id | BookId | CategoryId
-------------- ---------------------- -------------------------
1 | BookA 1 | CategoryA 1 | 1 | 1
2 | BookB 2 | CategoryB 2 | 1 | 2
3 | 2 | 2
首先,我嘗試了 LINQPad 6 應用程序中的代碼。 以下代碼在那里運行良好:
from bc in Books_Categories
join b in Books on bc.BookId equals b.Id
join category in Categories on bc.CategoryId equals category.Id
group category by new {b.Id, b.Name} into g
select new {
g.Key.Id,
g.Key.Name,
CategoryName = g.Select(x => x.Name)
}
運行代碼后的結果:
BookA | List<String> (CategoryA CategoryB)
BookB | List<String> (CategoryA)
我已將代碼集成到應用程序中,但出現我不理解的錯誤。 當我運行這個應用程序時:
public List<BookDetail> GetBookDetail()
{
using (EBookContext context = new EBookContext())
{
var result = (from bc in context.Books_Categories
join b in context.Books on bc.BookId equals b.Id
join c in context.Categories on bc.CategoryId equals c.Id
group c by new { b.Id, b.Name } into g
select new BookDetail
{
BookId = g.Key.Id,
BookName = g.Key.Name,
CategoryName = string.Join(",",g.Select(x => x.Name).ToList()) // CategoryName is a string variable.
});
return result.ToList();
}
}
我收到以下錯誤:
Select(x => x.Name)' 無法翻譯。 以可翻譯的形式重寫查詢,或通過插入對 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的調用顯式切換到客戶端評估。 有關詳細信息,請參閱https://go.microsoft.com/fwlink/?linkid=2101038 。
我怎么解決這個問題?
要了解問題的原因,您應該了解IEnumerable
和IQueryable
之間的區別。
實現IEnumerable<...>
的 object 表示一系列相似對象。 您可以獲得序列的第一項,只要您有一項,您就可以要求下一項。
IEnumerable 擁有執行此操作的所有內容。 通常 IEnumerable 應該由本地進程執行,因此它可以使用本地進程可以調用的所有方法。
枚舉序列 GetEnumerator 被調用並重復 MoveNext() / Current:
IEnumerable<Customer> customers = ...
using (IEnumerator<Customer> enumerator = customers.GetEnumerator()
{
while (enumerator.MoveNext())
{
// There is still a Customer
Customer customer = enumerator.Current;
ProcessCustomer(customer);
}
}
通常您不會使用這些低級方法進行枚舉。 使用諸如 foreach 之類的方法或使用不返回IEnumerable<...>
的 LINQ 方法(如 ToList、TDictionary、FirstOrDefault、Count、Any 等)枚舉該序列。
盡管 IQueryable 看起來與 IEnumerable 非常相似,但它並不代表一個序列,它代表了獲得可枚舉序列的潛力。
為此, IQueryable 包含一個Expression
和一個Provider
。 表達式以通用格式保存必須獲取的數據。 Provider 知道誰必須提供數據(通常是數據庫管理系統),以及用於與 DBMS 通信的語言(通常是 SQL)。
當您調用將枚舉 IQueryable 的方法時,將調用 GetEnumerator() / MoveNext() / Current 的深處。 表達式被發送給提供商,提供商會將其翻譯成 SQL 並從 DBMS 獲取數據。 返回的數據表示為IEnumerator<...>
,您可以調用其中的 MoveNext() / Current。
如上所述,只要調用 GetEnumeator / MoveNext,Expression 就會發送到 Provider,Provider 會嘗試將數據轉換為 SQL。
唉,Provider不知道你的本地函數,因此無法將它們翻譯成SQL。 盡管實體框架的開發人員做得很聰明,但幾個 .NET 方法也無法轉換為 SQL。 事實上,甚至有幾種 LINQ 方法不受支持。 請參閱支持和不支持的 LINQ 方法(LINQ 到實體) 。
在您的 Select 中,您使用String.Join
。 您的提供商不知道如何將其翻譯成 SQL。 編譯器不知道你的 Provider 有多聰明,所以編譯器不能抱怨。 您將在運行時看到問題。
考慮使用AsEnumerable
。 這會將選定的數據作為 Enumerable 序列移動到您可以使用 String.Join 的本地進程。
但是,使用 AsEnumerable 時要小心。 數據庫管理系統在搜索和組合表格方面得到了極大的優化。 將所選數據傳輸到本地進程是查詢中較慢的部分之一。 所以重寫你的查詢,這樣你就不會傳輸比實際使用更多的數據。
因此盡量避免在Where
之前使用AsEnumerable
,尤其是在 Where 過濾掉大部分獲取的項目時。
在您的查詢中,獲取每個 BookDetail 的所有類別並在本地加入它們似乎不是一個大問題:數據庫String.Join
不會限制已傳輸的字節數。
要獲取所有書籍,每本書都有其類別,我使用Queryable.GroupJoin的重載之一
var booksWithCategories = dbContext.Books.GroupJoin(dbContext.BooksCategories,
book => book.Id, // from every Book take the Id
bookCategory => bookCategory.BookId, // from every BookCategory take the foreign key
// Parameter resultSelector: from every Book, and all matching BookCategories
// make one new:
(book, bookCategories) => new
{
Id = book.Id,
Name = book.Name,
Categories = dbContext.Categories
.Where(category => bookCategories
.Select(bookcategory => bookcategory.CategoryId)
.Contains(category.Id))
.Select(category => new
{
// select the category properties that you want, for example:
Id = category.Id,
Name = category.Name,
}
.ToList(),
})
換句話說:從 DbContext.Books 序列中的每一本書中,獲取 Id。 從 DbContext.BookCategories 序列中的每個 bookcategory 中,獲取 BookId。 當它們匹配時,使用這本書及其所有匹配的 BookCategories 制作一個新的 object。 從書中獲取 ID 和名稱。
要獲取圖書的類別,請從屬於這本書的所有 BookCategories 中提取 CategoryId。 結果:屬於這本書的 CategoryId 序列。
現在獲取 DbContext.Categories 中的所有類別,並僅保留那些 ID 位於此 CategoryId 序列中的類別。 使用這些類別來 select 您想要的類別屬性。
在您的具體示例中,您只需要名稱,因此您可以將屬性類別更改為:
CategorieNames = dbContext.Categories
.Where(...) // same as above
.Select(category => caegory.Name).ToList(),
但我想加入這些字符串!
只要這些數據在內部使用,我不建議為它創建一個字符串。 這只會使重用獲取的數據變得更加困難。
因此,我建議盡可能長時間地將其保留為類別名稱列表,並且僅在您決定顯示它之前加入它:
繼續查詢
.AsEnumerable()
.Select(book => new
{
... // Book properties
CategoryNames = String.Join(", ", book.Categories);
}
嘗試.AsEnumerable()
:
var result = (from bc in context.Books_Categories.AsEnumerable()
join b in context.Books on bc.BookId equals b.Id
join c in context.Categories on bc.CategoryId equals c.Id
group c by new { b.Id, b.Name } into g
select new BookDetail
{
BookId = g.Key.Id,
BookName = g.Key.Name,
CategoryName = string.Join(",",g.Select(x => x.Name).ToList()) // CategoryName is a string variable.
});
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.