EntityFramework Core基本的增刪改查以及常用的方法


參考資料:
楊旭視頻:https://www.bilibili.com/video/BV1xa4y1v7rR?p=5

添加

與數據庫進行交互需要用到我們的數據庫上下文,我的是DemoDbContext。Context用完后需要對它進行清理資源,也就是調用它的Dispose方法,因為它實現了IDisposable接口。也可以直接使用using關鍵字,當方法走完時,Context就會被Dispose掉。

添加一條League:

class Program
{
    static void Main(string[] args)
    {
        using var context = new DemoDbContext();

        var serieA = new League
        {
            Country = "Italy",
            Name = "Serie A"
        };

        context.Leagues.Add(serieA);    // 此時與數據庫沒有任何交互
        var count = context.SaveChanges();  // 此時發生與數據庫交互
        Console.WriteLine(count);
    }
}

輸出結果為1。數據也添加成功了:

UTOOLS1593085922946.png

在調用context的SaveChanges方法時,Context會檢查所有它的對象的最終的狀態,有的對象可能新增了,有的對象可能修改了等。SaveChanges相當於在同一個事務里,針對它的變化,執行相應的SQL語句。如果執行失敗,會整體性回滾,執行成功會返回受影響的行數,這里就是1。

添加多筆數據,有兩種方式,一種是把多筆數據對應的多個對象作為AddRange方法的參數;另一種是new一個集合,把多筆數據放在集合里作為AddRange方法的參數:

context.Leagues.AddRange(serieB, serieC);   // 方法1
context.Leagues.AddRange(new List<League> { serieB, serieC });  // 方法2

添加多筆不同類型的數據:

using var context = new DemoDbContext();

var seriaA = context.Leagues.Single(x => x.Name == "Serie A");

var serieB = new League
{
    Country = "Italy",
    Name = "Serie B"
};

var serieC = new League
{
    Country = "Italy",
    Name = "Serie C"
};

var milan = new Club
{
    Name = "AC Milan",
    City = "Milan",
    DateOfEstablished = new DateTime(1899, 12, 16),
    League = seriaA
};

context.AddRange(serieB, serieC, milan);

var count = context.SaveChanges();
Console.WriteLine(count);

其中SeriaA是從數據庫中查詢到的Name為"Serie A"的實體。serieB, serieC與milan是不同類型,milan的League屬性是查詢出來的SeriaA。在添加多筆不同類型的數據到數據庫時,直接使用context.AddRange,省去了中間的League等DbSet。直接使用context也可以使用Add方法添加單筆數據。運行了一下是可以直接執行成功的。

UTOOLS1593095309828.png

可以看到先進行了一次查詢,又進行了三次INSERT,是符合預期的。

輸出執行的SQL語句

上面我們在控制台中輸出了SQL語句,如何做到的呢?我們在SaveChanges時,想要在控制台日志中輸出執行的SQL語句。回到數據庫Context類中,加一個靜態只讀的日志工廠:

public static readonly ILoggerFactory ConsoleLoggerFactory =
    LoggerFactory.Create(builder =>
    {
        builder.AddFilter((category, level) =>
            category == DbLoggerCategory.Database.Command.Name
            && level == LogLevel.Information)
        .AddConsole();
    });

其中AddConsole會有紅色下划線,我們需要在數據庫上下文所在的項目中安裝Microsoft.Extensions.Logging.Console這個Nuget包。

再看上面這個ILoggerFactory類的屬性,.Net Core的日志系統會輸出很多日志,我們加了一個過濾器過濾一下。范疇(category)為DbLoggerCategory.Database.Command.Name,等級(level)為Information。

然后修改一下重寫的OnConfiguring,在UseSqlServer前面加上UseLoggerFactory:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseLoggerFactory(ConsoleLoggerFactory)
        .UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=YangDemo;Integrated Security=True");
}

再次運行程序就會在控制台輸出日志:

UTOOLS1593086989644.png

查詢

查詢Leagues表中的所有數據:

using var context = new DemoDbContext();

var leagues = context.Leagues.Where(l => l.Country == "Italy").AsEnumerable();
// var leagues = context.Leagues.Where(l => l.Country == "Italy").ToList();  // AsEnumerable與ToList結果相同,但有不同之處

foreach (League league in leagues)
{
    Console.WriteLine(league.Name);
}

其中AsEnumerable和ToList的不同之處參見:https://www.cnblogs.com/Kit-L/p/13191498.html#asenumerable方法

如果不寫ToList或者AsEnumerable等枚舉查詢結果集合的語句的話,只相當於組建了SQL查詢語句,並不執行。下列語句是用查詢表達式形式來進行查詢,但一般沒人這樣用。

var leagues2 = (from league in context.Leagues
                where league.Country == "Italy"
                select league).ToList();

也可以先不ToList,而是直接用for循環來遍歷查詢,但這種方式最好是for循環中的操作很簡單很快,否則會產生數據沖突等問題。一般也不應該這樣做,最好是先ToList再從結果中讀取數據。

可以看到我們查詢中的條件"Italy"是寫死的,我們可以用一個參數來替換,這樣生成的SQL語句中的"Italy"也會變成參數:

var italy = "Italy";
var leagues = context.Leagues.Where(l => l.Country == italy).AsEnumerable();

看一下控制台輸出的SQL語句:

UTOOLS1593096675670.png

可以看到變成了參數的形式,本來是字符串的形式。但可以看到itely的參數值是一個問號,但我們傳進去的是"Italy"。這是因為默認情況下,EF Core不會把參數輸出到日志中。可以回數據庫上下文類中進行修改使其顯示:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseLoggerFactory(ConsoleLoggerFactory)

        .EnableSensitiveDataLogging()   //

        .UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=YangDemo;Integrated Security=True");
}

模糊查詢

如何實現SQL語句的Country LIKE %e%這種模糊查詢操作?可以使用字符串的Contains方法:

var leagues = context.Leagues
    .Where(l => l.Country.Contains("e"))
    .AsEnumerable();

還有另一種寫法:

var leagues2 = context.Leagues
    .Where(x => EF.Functions.Like(x.Country, "%e%"))
    .ToList();

重要:ToList等執行對數據庫的操作的方法

總結一下哪些常用的方法可以執行對數據庫的操作:

ToList():返回集合。

First()、FirstOrDefault():返回單筆數據,返回滿足條件的第一筆數據。
First()必須有數據,沒有就報錯,FirstOrDefault()可以有數據也可以沒有數據。最終如果要輸出的話,可以用?.的方式,如league?.Name。First()等括號中可以直接寫條件,常常可以省略Where()這一塊。

Single():符合查詢條件的只能是一個數據。
SingleOrDefault():符合查詢條件的只能是一個數據或者沒有數據。

Last()、LastOrDefault():最后一筆數據,其他特征同上。想使用它們,必須進行排序,比如使用OrderBy()或OrderByDescending()。

Count()、LongCount():查詢出來的Count的結果。統計個數。

Min()、Max():最小最大值。

Average():平均值。

Sum():求和。

Find():它不是LINQ方法,是DbSet的方法,但也會執行查詢動作。

還有這些方法的異步版本,比如ToListAsync()、FirstAsync()等,還有SaveChangesAsync()。

刪除

EF Core只能刪除被Context追蹤的數據,而數據只有先查詢出來,才能被追蹤。也就是說不查詢出來,是無法刪除的。

我們知道Clubs表中有一筆數據"AC Milan",我們先查出來再刪掉它:

var milan = context.Clubs.Single(x => x.Name == "AC Milan");    // 追蹤數據

// 調用刪除方法的多種形式
context.Clubs.Remove(milan);
context.Remove(milan);
context.Clubs.RemoveRange(milan, milan);
context.RemoveRange(milan, milan);

var count = context.SaveChanges();
Console.WriteLine(count);

如果想使用SQL語句或存儲過程對數據庫進行Delete動作的話,EF Core也是支持的。

修改

與刪除一樣,首先數據要被context追蹤,才可以修改。:

using var context = new DemoDbContext();

var league = context.Leagues.First();

league.Name += "~~";    // 此時context就知道這個屬性已經被修改了,league對象的狀態就是Modified,再SaveChange就可以把所有修改操作到數據庫

var count = context.SaveChanges();

Console.WriteLine(count);

修改多條數據:

例如:

var leagues = context.Leagues.Skip(1).Take(3).ToList();

foreach (var league in leagues)
{
    league.Name += "~~";
}

var count = context.SaveChanges();

而在實際應用場景中,修改的數據往往都是前后端分離的系統架構里,從前端傳過來的JSON,反序列化之后變成一個C#類。此時這些數據都不是context查詢出來的,自然也就沒追蹤,也就沒法進行修改。

using var context = new DemoDbContext();

var league = context.Leagues.AsNoTracking().First();    // AsNoTracking()

league.Name += "++";

context.Leagues.Update(league);     // 進行追蹤

var count = context.SaveChanges();

Console.WriteLine(count);

使用AsNoTracking()來模擬前端傳回的數據,AsNoTracking顧名思義,就是不進行追蹤,查完就跟context沒有任何關系了。league就相當於前端的JSON傳進來的。修改了league.Name之后,如何再次讓它被Context追蹤?使用Update()方法將數據傳進去就可以了。它可以將league的狀態設置成modified狀態。

Update()同樣也有UpdateRange()方法,context上也有這個UpDate()方法,也就是說可以直接context.Update(league)

UTOOLS1593099970333.png

執行完發現我們雖然只修改了Name屬性,但Country屬性也重設了一遍,這是因為重新進行追蹤后,league的所有屬性都處於modified狀態,相當於都修改過。

也可以只修改Name這一個屬性,開銷似乎會小很多,可以查閱官方文檔

不追蹤-AsNoTracking()

不加AsNoTracking(),context查詢的數據都將被它追蹤,而且是不斷變化地追蹤。既然有變化追蹤,就會不斷消耗CPU和內存,開銷有點大。

如果查詢的數據量比較大,而且不需要進行變化追蹤,可以加上AsNoTracking()。執行完AsNoTracking(),依然處於沒有執行對數據庫查詢地動作的狀態,直到遇到ToList()等枚舉查詢結果的方法才執行。

AsNoTracking()可以跟我們前面用的時候一樣單獨設置,也可以進行全局的設置。去數據庫上下文中的構造函數中添加:

public DemoDbContext()
{
    ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}

這樣就算是在這個上下文的全局進行設置了,context將不會再追蹤,也不用單獨再在查詢中寫AsNoTracking()。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM