asp.net core系列 32 EF查詢(xún)數(shù)據(jù) 必備知識(shí)(1)

一.查詢(xún)的工作原理

Entity Framework Core 使用語(yǔ)言集成查詢(xún) (LINQ) 來(lái)查詢(xún)數(shù)據(jù)庫(kù)中的數(shù)據(jù)。 通過(guò) LINQ 可使用 C#(或你選擇的其他 .NET 語(yǔ)言)基于派生上下文和實(shí)體類(lèi)編寫(xiě)強(qiáng)類(lèi)型查詢(xún)。 LINQ 查詢(xún)的表示形式會(huì)傳遞給數(shù)據(jù)庫(kù)提供程序,進(jìn)而轉(zhuǎn)換為特定的數(shù)據(jù)庫(kù)查詢(xún)語(yǔ)言(例如,適用于關(guān)系數(shù)據(jù)庫(kù)的 SQL)。

1.1 查詢(xún)的生命周期, 下面是每個(gè)查詢(xún)所經(jīng)歷的過(guò)程概述:

(1) LINQ 查詢(xún)由 E F處理,用于生成已準(zhǔn)備好的表示形式,由數(shù)據(jù)庫(kù)提供程序處理。緩存結(jié)果,以便每次執(zhí)行查詢(xún)時(shí)都不需要執(zhí)行此處理。
(2) 結(jié)果將傳遞給數(shù)據(jù)庫(kù)提供程序

a.數(shù)據(jù)庫(kù)提供程序會(huì)識(shí)別出查詢(xún)的哪些部分可以在數(shù)據(jù)庫(kù)中求值。
b. 查詢(xún)的這些部分會(huì)轉(zhuǎn)換為特定數(shù)據(jù)庫(kù)的查詢(xún)語(yǔ)言(例如,關(guān)系數(shù)據(jù)庫(kù)的 SQL)
c. 將一個(gè)或多個(gè)查詢(xún)發(fā)送到數(shù)據(jù)庫(kù)并返回結(jié)果集(結(jié)果是來(lái)自數(shù)據(jù)庫(kù)的值,而不是實(shí)體實(shí)例)

(3) 返回結(jié)果集處理

a.如果這是跟蹤查詢(xún),EF會(huì)檢查數(shù)據(jù)是否代表一個(gè)實(shí)體,已存在于上下文實(shí)例的更改跟蹤器中。
如果是,則會(huì)返回現(xiàn)有實(shí)體
如果不是,則會(huì)創(chuàng)建新實(shí)體、設(shè)置更改跟蹤并返回該新實(shí)體
b.如果這是非跟蹤查詢(xún),EF 會(huì)檢查數(shù)據(jù)是否表示此查詢(xún)結(jié)果集中的現(xiàn)有實(shí)體
如果是,則會(huì)返回現(xiàn)有實(shí)體
如果不是,則會(huì)創(chuàng)建新實(shí)體并返回該新實(shí)體

1.2 執(zhí)行查詢(xún)時(shí):

調(diào)用LINQ運(yùn)算符時(shí),只會(huì)構(gòu)建查詢(xún)?cè)趦?nèi)存中的表示形式。 只有在使用結(jié)果時(shí),查詢(xún)才會(huì)發(fā)送到數(shù)據(jù)庫(kù)。觸發(fā)查詢(xún)發(fā)送到數(shù)據(jù)庫(kù)的最常見(jiàn)操作如下:
 (1) 在 for 循環(huán)中循環(huán)訪問(wèn)結(jié)果

var blogs = from b in BloggingContext.Blogs
                     select {....}

            //觸發(fā)數(shù)據(jù)庫(kù)查詢(xún)
            foreach (var item in blogs)
            {
                int maxID = item.ID;
            }

(2) 使用 ToList、ToArray、Single、Count 等操作都會(huì)觸發(fā)數(shù)據(jù)庫(kù)查詢(xún)

 BloggingContext.Blogs.ToList();
 BloggingContext.Blogs.ToArray();
 BloggingContext.Blogs.Count();
 BloggingContext.Blogs.Single();
 BloggingContext.Blogs.First();

(3) 將查詢(xún)結(jié)果數(shù)據(jù)綁定到 UI

二.LINQ 查詢(xún)

Entity Framework Core 使用語(yǔ)言集成查詢(xún) (LINQ) 來(lái)查詢(xún)數(shù)據(jù)庫(kù)中的數(shù)據(jù)。 通過(guò) LINQ 可使用 C#(或你選擇的其他 .NET 語(yǔ)言)基于派生上下文和實(shí)體類(lèi)編寫(xiě)強(qiáng)類(lèi)型查詢(xún)。 LINQ 查詢(xún)的表示形式會(huì)傳遞給數(shù)據(jù)庫(kù)提供程序,進(jìn)而轉(zhuǎn)換為特定的數(shù)據(jù)庫(kù)查詢(xún)語(yǔ)言(例如,適用于關(guān)系數(shù)據(jù)庫(kù)的 SQL)。

// (1)加載所有數(shù)據(jù)
     var blogs = BloggingContext.Blogs.ToList();
 SELECT [b].[BlogId], [b].[Name], [b].[Title], [b].[Url] FROM [Blogs] AS [b]
 //(2)加載單個(gè)實(shí)體
     var blog = BloggingContext.Blogs.Single(b => b.BlogId == 1);
SELECT TOP(2) [b].[BlogId], [b].[Name], [b].[Title], [b].[Url]
    FROM [Blogs] AS [b]
    WHERE [b].[BlogId] = 1
   //(3)篩選
    var blogs = BloggingContext.Blogs.Where(b => b.Url.Contains("dotnet")).ToList();
SELECT [b].[BlogId], [b].[Name], [b].[Title], [b].[Url]
  FROM [Blogs] AS [b]
  WHERE CHARINDEX(N'dotnet', [b].[Url]) > 0
 //(4)排序
  var blogs = BloggingContext.Blogs.OrderByDescending(b => b.BlogId).Select(b=> new { b.BlogId,b.Name }).ToList();
SELECT [b].[BlogId], [b].[Name]
  FROM [Blogs] AS [b]
  ORDER BY [b].[BlogId] DESC
//(5) group  找出重復(fù)的url,取出最大BlogId
     var blogs = from b in BloggingContext.Blogs
                        group b by new { b.Url} into gs
                        where gs.Count() >1 
                        select new
                        {
                            ID= gs.Max(b=>b.BlogId)
                        };
       //top 1
     int maxID = blogs.First().ID;
 SELECT TOP(1) MAX([b].[BlogId]) AS [ID]
    FROM [Blogs] AS [b]
    GROUP BY [b].[Url]
    HAVING COUNT(*) > 1
// (6)多表join查詢(xún)
       var query = from b in context.Blogs
                        join p in context.Posts  on  b.BlogId equals p.BlogId
                        where b.BlogId == 1
                        select new { b.Name,p.Title } ;
       var bloglinq= query.ToList();
 SELECT [b].[Name], [p].[Title]
    FROM [Blogs] AS [b]
    INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]
    WHERE [b].[BlogId] = 1

有關(guān)顯示 LINQ 可完成的任務(wù)的大量示例,請(qǐng)參閱 101 個(gè) LINQ 示例

三. 客戶(hù)端求值

EF支持部分查詢(xún)?cè)诳蛻?hù)端上求值,而將其他部分推送到數(shù)據(jù)庫(kù)執(zhí)行。 由數(shù)據(jù)庫(kù)提供程序確定查詢(xún)的哪些部分會(huì)在數(shù)據(jù)庫(kù)中求值。 下面示例中 客戶(hù)端通過(guò)執(zhí)行StandardizeUrl方法來(lái)返回 URL,查詢(xún)的其余部分都是在數(shù)據(jù)庫(kù)中執(zhí)行的。

var blogs = context.Blogs
    .OrderByDescending(blog => blog.Rating)
    .Select(blog => new
    {
        Id = blog.BlogId,
        Url = StandardizeUrl(blog.Url)
    })
    .ToList();


    public static string StandardizeUrl(string url)
    {
    url = url.ToLower();

    if (!url.StartsWith("http://"))
    {
        url = string.Concat("http://", url);
    }

    return url;
    }
3.1 可能的性能問(wèn)題

雖然客戶(hù)端求值非常有用,但在某些情況下可能會(huì)導(dǎo)致性能不佳。 請(qǐng)考慮以下查詢(xún),該where中使用輔助方法。 由于無(wú)法在數(shù)據(jù)庫(kù)中執(zhí)行此操作,因此blog的所有數(shù)據(jù)將被拉入內(nèi)存中,然后會(huì)在客戶(hù)端上應(yīng)用篩選器。 根據(jù)數(shù)據(jù)量以及過(guò)濾掉多少數(shù)據(jù),可能會(huì)導(dǎo)致性能下降。

  var blogs = context.Blogs
    .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
    .ToList();
3.2 為客戶(hù)端評(píng)估拋出異常

默認(rèn)情況下,當(dāng)執(zhí)行客戶(hù)端求值時(shí),EF Core 將記錄警告在日志中。可以改為引發(fā)異?;虿粓?zhí)行任何操作。 設(shè)置如下所示

services.AddDbContext<BloggingContext>
                (options => 
                options.UseSqlServer(connection)
                //改為引發(fā)異常
                .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
                );

四. 跟蹤與非跟蹤查詢(xún)

跟蹤行為可控制 EF是否將有關(guān)實(shí)體實(shí)例的信息保留在其更改跟蹤器中。 如果已跟蹤某個(gè)實(shí)體,則該實(shí)體中檢測(cè)到的任何更改都會(huì)在 SaveChanges() 期間永久保存到數(shù)據(jù)庫(kù)。 EF 還會(huì)修正從跟蹤查詢(xún)中獲取的實(shí)體與先前已加載到 DbContext 實(shí)例中的實(shí)體兩者之間的導(dǎo)航屬性。

4.1 跟蹤查詢(xún)

默認(rèn)情況下,會(huì)跟蹤返回實(shí)體類(lèi)型的查詢(xún)。 這表示可以更改這些實(shí)體實(shí)例,然后通過(guò) SaveChanges() 持久化這些更改。在以下示例中,將檢測(cè)到對(duì)Blog評(píng)分所做的更改,并在 SaveChanges() 期間將這些更改持久化到數(shù)據(jù)庫(kù)中。

var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
        blog.Rating = 5;
        context.SaveChanges();

        //顯示設(shè)置與上面一樣,開(kāi)啟了跟蹤查詢(xún)
        var blog = context.Blogs. AsTracking().SingleOrDefault(b => b.BlogId == 1);
4.2 非跟蹤查詢(xún)

只需要讀取數(shù)據(jù)結(jié)果方案時(shí),非跟蹤查詢(xún)十分有用。 可以更快速地執(zhí)行非跟蹤查詢(xún),因?yàn)闊o(wú)需設(shè)置更改跟蹤信息。

//設(shè)置當(dāng)前查詢(xún)?yōu)榉歉櫜樵?xún)
    var blogs = context.Blogs
        .AsNoTracking()
        .ToList();

    //還可以在上下文實(shí)例級(jí)別, 設(shè)置默認(rèn)為非跟蹤查詢(xún)
        using (var context = new BloggingContext())
        {
            context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

            var blogs = context.Blogs.ToList();
        }
4.3跟蹤和投影

即使查詢(xún)的結(jié)果類(lèi)型不是實(shí)體類(lèi)型,只要結(jié)果包含實(shí)體類(lèi)型,則默認(rèn)情況下也會(huì)跟蹤這些實(shí)體類(lèi)型。 在以下返回匿名類(lèi)型的查詢(xún)中,會(huì)跟蹤結(jié)果集中 Blog 的實(shí)例。

var blog = context.Blogs
        .Select(b =>
            new
            {
                Blog = b,
                Posts = b.Posts.Count()
            });

如果結(jié)果集不包含任何實(shí)體類(lèi)型,則不會(huì)執(zhí)行跟蹤。 在以下返回匿名類(lèi)型(具有實(shí)體中的某些值,但沒(méi)有實(shí)際實(shí)體類(lèi)型的實(shí)例)的查詢(xún)中,不會(huì)執(zhí)行跟蹤。

var blog = context.Blogs
        .Select(b =>
            new
            {
                Id = b.BlogId,
                Url = b.Url
            });

參考文獻(xiàn)

EF查詢(xún)數(shù)據(jù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容