1.複雜查詢運算符 在生產場景中,我們經常用到LINQ運算符進行查詢獲取數據,現在我們就來瞭解下生產場景經常出現幾種複雜查詢運算符。 1.1聯接(INNER JOIN) 藉助LINQ Join運算符,可根據每個源的鍵選擇器連接兩個數據源,併在鍵匹配時生成值的元組。 SQL: SQL Server P ...
1.複雜查詢運算符
在生產場景中,我們經常用到LINQ運算符進行查詢獲取數據,現在我們就來瞭解下生產場景經常出現幾種複雜查詢運算符。
1.1聯接(INNER JOIN)
藉助LINQ Join運算符,可根據每個源的鍵選擇器連接兩個數據源,併在鍵匹配時生成值的元組。
var query = from blog in _context.Set<Blog>() join post in _context.Set<Post>() on blog.BlogId equals post.BlogId select new { blog, post };
SQL:
SELECT [blog].[BlogId], [blog].[Createtime], [blog].[Updatetime], [blog].[Url], [post].[PostId], [post].[BlogId], [post].[Content], [post].[Title]
FROM [Blog] AS [blog]
INNER JOIN [Post] AS [post] ON [blog].[BlogId] = [post].[BlogId]
SQL Server Profiler:
1.2左聯接(Left Join)
雖然Left Join不是LINQ運算符,但關係資料庫具有常用於查詢的Left Join的概念。LINQ查詢中的特定模式提供與伺服器上的LEFT JOIN相同的結果。
var query = from blog in _context.Set<Blog>() join post in _context.Set<Post>() on blog.BlogId equals post.BlogId into grouping from post in grouping.DefaultIfEmpty() select new { blog, post };
SQL:
SELECT [blog].[BlogId], [blog].[Createtime], [blog].[Updatetime], [blog].[Url], [post].[PostId], [post].[BlogId], [post].[Content], [post].[Title]
FROM [Blog] AS [blog]
LEFT JOIN [Post] AS [post] ON [blog].[BlogId] = [post].[BlogId]
SQL Server Profiler:
1.3分組(GroupBy)
LINQ GroupBy運算符創建IGrouping<TKey, TElement>類型的結果,其中TKey和TElement可以是任意類型。此外,IGrouping實現了IEnumerable<TElement>,這意味著可在分組後使用任意LINQ運算符來對其進行組合。
var query = from blog in _context.Set<Blog>() group blog by blog.Url into g select new { g.Key, Count = g.Count() };
SQL:
SELECT [blog].[Url] AS [Key], COUNT(*) AS [Count]
FROM [Blog] AS [blog]
GROUP BY [blog].[Url]
SQL Server Profiler:
分組的聚合運算符出現在Where或OrderBy(或其他排序方式)LINQ運算符中。它在SQL中將Having子句用於Where子句。
var query = from blog in _context.Set<Blog>() group blog by blog.Url into g where g.Count() > 0 orderby g.Key select new { g.Key, Count = g.Count() };
SQL:
SELECT [blog].[Url] AS [Key], COUNT(*) AS [Count] FROM [Blog] AS [blog] GROUP BY [blog].[Url] HAVING COUNT(*) > 0 ORDER BY [Key]
SQL Server Profiler:
EF Core支持的聚合運算符如下所示:
●Avg
●Count
●LongCount
●Max
●Min
●Sum
1.4SelectMany
藉助LINQ SelectMany運算符,可為每個外部元素枚舉集合選擇器,並從每個數據源生成值的元組。
var query0 = from b in _context.Set<Blog>() from p in _context.Set<Post>() select new { b, p }; var query1 = from b in _context.Set<Blog>() from p in _context.Set<Post>().Where(p => b.BlogId == p.BlogId).DefaultIfEmpty() select new { b, p }; var query2 = from b in _context.Set<Blog>() from p in _context.Set<Post>().Select(p => b.Url + "=>" + p.Title).DefaultIfEmpty() select new { b, p };
SQL:
SELECT [b].[BlogId], [b].[Createtime], [b].[Updatetime], [b].[Url], [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title] FROM [Blog] AS [b] CROSS JOIN [Post] AS [p] SELECT [b].[BlogId], [b].[Createtime], [b].[Updatetime], [b].[Url], [t0].[PostId], [t0].[BlogId], [t0].[Content], [t0].[Title] FROM [Blog] AS [b] CROSS APPLY ( SELECT [t].[PostId], [t].[BlogId], [t].[Content], [t].[Title] FROM ( SELECT NULL AS [empty] ) AS [empty] LEFT JOIN ( SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title] FROM [Post] AS [p] WHERE [b].[BlogId] = [p].[BlogId] ) AS [t] ON 1 = 1 ) AS [t0] SELECT [b].[BlogId], [b].[Createtime], [b].[Updatetime], [b].[Url], [t0].[c] FROM [Blog] AS [b] CROSS APPLY ( SELECT [t].[c] FROM ( SELECT NULL AS [empty] ) AS [empty] LEFT JOIN ( SELECT ([b].[Url] + N'=>') + [p].[Title] AS [c] FROM [Post] AS [p] ) AS [t] ON 1 = 1 ) AS [t0]
SQL Server Profiler:
好了,這裡就不多寫關於LINQ其他示例了,如果需要瞭解的小伙伴們,可以移步“101個LINQ示例”這裡瞭解。
2.原生SQL查詢
有一些複雜業務場景,使用LINQ查詢可能會導致SQL查詢效率低下並不適用,那麼這時候就需要到原生SQL查詢了。EF Core為我們提供FromSql擴展方法基於原始SQL查詢。 FromSql只能在直接位於DbSet<>上的查詢根上使用。
var blogs = _context.Blog.FromSql("SELECT * FROM dbo.Blog").ToList();
原生SQL查詢可用於執行存儲過程。
var blogs = _context.Blog .FromSql("EXECUTE dbo.GetMostPopularBlogs") .ToList();
2.1原始SQL查詢使用參數化
向原始SQL查詢引入任何用戶提供的值時,必須註意防範SQL註入攻擊。除了驗證確保此類值不包含無效字元,還要將值與SQL文本參數化處理。
下麵的示例通過在SQL查詢字元串中包含形參占位符並提供額外的實參,將單個形參傳遞到存儲過程。雖然此語法可能看上去像String.Format語法,但提供的值包裝在DbParameter中,且生成的參數名稱插入到指定{0}占位符的位置。
var url = "http://blogs.msdn.com/webdev"; var blogs = _context.Blog .FromSql("EXECUTE dbo.GetMostPopularBlogForUrl {0}", url) .ToList();
SQL:
exec sp_executesql N'EXECUTE dbo.GetMostPopularBlogForUrl @p0 ',N'@p0 nvarchar(4000)',@p0=N'http://blogs.msdn.com/webdev'
SQL Server Profiler:
還可以構造DbParameter並將其作為參數值提供。由於使用了常規SQL參數占位符而不是字元串占位符,因此可安全地使用FromSql:
var urlParams = new SqlParameter("Url", "http://blogs.msdn.com/webdev"); var blogs = _context.Blog .FromSql("EXECUTE dbo.GetMostPopularBlogForUrl @Url", urlParams) .ToList();
SQL:
exec sp_executesql N'EXECUTE dbo.GetMostPopularBlogForUrl @Url ',N'@Url nvarchar(28)',@Url=N'http://blogs.msdn.com/webdev'
SQL Server Profiler:
2.2使用LINQ編寫SQL
可使用LINQ運算符在初始的原始SQL查詢基礎上進行組合。EF Core將其視為子查詢,併在資料庫中對其進行組合。下麵的示例使用原始SQL查詢,該查詢從表值函數 (TVF) 中進行選擇。然後,使用LINQ進行篩選和排序,從而對其進行組合。
var searchTerm = "http://blogs.msdn.com/visualstudio"; var blogs = _context.Blog .FromSql($"SELECT * FROM dbo.Blog") .Where(b => b.Url == searchTerm) .Include(c=>c.Post) .OrderByDescending(b => b.Createtime) .ToList();
SQL:
exec sp_executesql N'SELECT [b].[BlogId], [b].[Createtime], [b].[Updatetime], [b].[Url] FROM ( SELECT * FROM dbo.Blog ) AS [b] WHERE [b].[Url] = @__searchTerm_1 ORDER BY [b].[Createtime] DESC, [b].[BlogId]',N'@__searchTerm_1 nvarchar(4000)',@__searchTerm_1=N'http://blogs.msdn.com/visualstudio' exec sp_executesql N'SELECT [b.Post].[PostId], [b.Post].[BlogId], [b.Post].[Content], [b.Post].[Title] FROM [Post] AS [b.Post] INNER JOIN ( SELECT [b0].[BlogId], [b0].[Createtime] FROM ( SELECT * FROM dbo.Blog ) AS [b0] WHERE [b0].[Url] = @__searchTerm_1 ) AS [t] ON [b.Post].[BlogId] = [t].[BlogId] ORDER BY [t].[Createtime] DESC, [t].[BlogId]',N'@__searchTerm_1 nvarchar(4000)',@__searchTerm_1=N'http://blogs.msdn.com/visualstudio'
SQL Server Profiler:
3.非同步查詢
當在資料庫中執行查詢時,非同步查詢可避免阻止線程。非同步查詢對於在客戶端應用程式中保持響應式UI非常重要。 非同步查詢還可以增加Web應用程式中的吞吐量,即通過釋放線程,以處理其他Web應用程式中的請求。
public async Task<IActionResult> Index() { var id1 = Thread.CurrentThread.ManagedThreadId.ToString(); var blogs = await _context.Blog.ToListAsync(); var id2 = Thread.CurrentThread.ManagedThreadId.ToString(); return View(blogs); }
當我們運行以上代碼時候,通過在關鍵字await上下文加入兩段獲取線程ID代碼,我們會看到如下結果:
看到兩段線程代碼輸出ID結果沒有?從上圖可以觀察到,當我們在進入某個視圖或者方法時候,執行到await某一個方法,當前線程不會一直等待下去,會立馬回收到線程池,供其他地方調用!當該await方法返回數據時候,才從線程池調用空閑線程執行await方法下文餘下的步驟。所以UI界面才不會進入假死狀態。
參考文獻:
複雜查詢運算符
原生SQL查詢
非同步查詢