添加
與數據庫進行交互需要用到我們的數據庫上下文,我的是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。數據也添加成功了:
在調用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方法添加單筆數據。運行了一下是可以直接執行成功的。
可以看到先進行了一次查詢,又進行了三次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");
}
再次運行程序就會在控制台輸出日志:
查詢
查詢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語句:
可以看到變成了參數的形式,本來是字符串的形式。但可以看到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)
。
執行完發現我們雖然只修改了Name屬性,但Country屬性也重設了一遍,這是因為重新進行追蹤后,league的所有屬性都處於modified狀態,相當於都修改過。
也可以只修改Name這一個屬性,開銷似乎會小很多,可以查閱官方文檔。
不追蹤-AsNoTracking()
不加AsNoTracking(),context查詢的數據都將被它追蹤,而且是不斷變化地追蹤。既然有變化追蹤,就會不斷消耗CPU和內存,開銷有點大。
如果查詢的數據量比較大,而且不需要進行變化追蹤,可以加上AsNoTracking()。執行完AsNoTracking(),依然處於沒有執行對數據庫查詢地動作的狀態,直到遇到ToList()等枚舉查詢結果的方法才執行。
AsNoTracking()可以跟我們前面用的時候一樣單獨設置,也可以進行全局的設置。去數據庫上下文中的構造函數中添加:
public DemoDbContext()
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
這樣就算是在這個上下文的全局進行設置了,context將不會再追蹤,也不用單獨再在查詢中寫AsNoTracking()。