練習 - 與資料互動
在上一個練習中,您已建立實體類別和資料庫內容。 您接著使用 EF Core 移轉,來建立資料庫結構描述。
在此練習中,您會完成 PizzaService
實作。 此服務會使用 EF Core,在資料庫上執行 CRUD 作業。
撰寫 CRUD 作業的程式碼
若要完成 PizzaService
實作,請在 Services\PizzaService.cs 中完成下列步驟:
如此範例中所示進行以下變更:
- 新增
using ContosoPizza.Data;
指示詞。 - 新增
using Microsoft.EntityFrameworkCore;
指示詞。 - 在建構函式之前為
PizzaContext
新增類別層級的欄位。 - 變更建構函式方法簽章以接受
PizzaContext
參數。 - 變更建構函式方法程式碼,將參數指派給此欄位。
using ContosoPizza.Models; using ContosoPizza.Data; using Microsoft.EntityFrameworkCore; namespace ContosoPizza.Services; public class PizzaService { private readonly PizzaContext _context; public PizzaService(PizzaContext context) { _context = context; } /// ... /// CRUD operations removed for brevity /// ... }
您稍早新增至 Program.cs 的
AddSqlite
方法呼叫已將PizzaContext
註冊為相依性插入。 建立PizzaService
執行個體時,會將PizzaContext
插入建構函式中。- 新增
以下列程式碼取代
GetAll
方法:public IEnumerable<Pizza> GetAll() { return _context.Pizzas .AsNoTracking() .ToList(); }
在上述程式碼中:
Pizzas
集合包含披薩資料表中的所有資料列。AsNoTracking
擴充方法會指示 EF Core 停用變更追蹤。 此為唯讀操作,因此AsNoTracking
可以最佳化效能。- 都會以
ToList
傳回所有披薩。
以下列程式碼取代
GetById
方法:public Pizza? GetById(int id) { return _context.Pizzas .Include(p => p.Toppings) .Include(p => p.Sauce) .AsNoTracking() .SingleOrDefault(p => p.Id == id); }
在上述程式碼中:
Include
擴充方法採用 lambda 運算式,透過使用積極式載入,指定在結果中包含Toppings
和Sauce
瀏覽屬性。 如果缺少此運算式,EF Core 會針對這些屬性傳回null
。SingleOrDefault
方法會傳回符合 Lambda 運算式的披薩。- 如果沒有相符的記錄,則會傳回
null
。 - 如果有多個相符記錄,則會擲回例外狀況。
- Lambda 運算式會描述
Id
屬性等於id
參數的記錄。
- 如果沒有相符的記錄,則會傳回
以下列程式碼取代
Create
方法:public Pizza Create(Pizza newPizza) { _context.Pizzas.Add(newPizza); _context.SaveChanges(); return newPizza; }
在上述程式碼中:
- 會假設
newPizza
是有效的物件。 EF Core 不會執行資料驗證,因此 ASP.NET Core 執行階段或使用者程式碼必須處理任何驗證。 Add
方法會將newPizza
實體新增至 EF Core 的物件圖。SaveChanges
方法會指示 EF Core,將物件變更保存到資料庫。
- 會假設
以下列程式碼取代
AddTopping
方法:public void AddTopping(int pizzaId, int toppingId) { var pizzaToUpdate = _context.Pizzas.Find(pizzaId); var toppingToAdd = _context.Toppings.Find(toppingId); if (pizzaToUpdate is null || toppingToAdd is null) { throw new InvalidOperationException("Pizza or topping does not exist"); } if(pizzaToUpdate.Toppings is null) { pizzaToUpdate.Toppings = new List<Topping>(); } pizzaToUpdate.Toppings.Add(toppingToAdd); _context.SaveChanges(); }
在上述程式碼中:
- 使用
Find
建立對現有Pizza
和Topping
物件的參考。 - 會使用
.Add
方法,將Topping
物件新增至Pizza.Toppings
集合。 如果新的集合不存在,則會建立新的集合。 SaveChanges
方法會指示 EF Core,將物件變更保存到資料庫。
- 使用
以下列程式碼取代
UpdateSauce
方法:public void UpdateSauce(int pizzaId, int sauceId) { var pizzaToUpdate = _context.Pizzas.Find(pizzaId); var sauceToUpdate = _context.Sauces.Find(sauceId); if (pizzaToUpdate is null || sauceToUpdate is null) { throw new InvalidOperationException("Pizza or sauce does not exist"); } pizzaToUpdate.Sauce = sauceToUpdate; _context.SaveChanges(); }
在上述程式碼中:
- 使用
Find
建立對現有Pizza
和Sauce
物件的參考。Find
是經過最佳化的方法,可依主索引鍵查詢記錄。Find
在查詢資料庫前,會先搜尋本地實體圖。 - 會將
Pizza.Sauce
屬性設定為Sauce
物件。 - EF Core 偵測到您在
Pizza
上設定Sauce
屬性,因此無須使用Update
方法呼叫。 SaveChanges
方法會指示 EF Core,將物件變更保存到資料庫。
- 使用
以下列程式碼取代
DeleteById
方法:public void DeleteById(int id) { var pizzaToDelete = _context.Pizzas.Find(id); if (pizzaToDelete is not null) { _context.Pizzas.Remove(pizzaToDelete); _context.SaveChanges(); } }
在上述程式碼中:
Find
方法會依主索引鍵擷取披薩 (在此案例中為Id
)。Remove
方法會在 EF Core 物件圖中移除pizzaToDelete
實體。SaveChanges
方法會指示 EF Core,將物件變更保存到資料庫。
儲存您所有的變更並執行
dotnet build
。 修正發生的任何錯誤。
植入資料庫
您已為 PizzaService
撰寫 CRUD 作業的程式碼,但如果資料庫包含良好的資料,則測試 Read 作業會比較容易。 您決定修改應用程式,以在啟動時植入資料庫。
警告
此資料庫植入程式碼沒有考慮到競爭條件,在不影響變更的情況下,於分散式環境中使用時請小心。
在 [資料] 資料夾中,請新增名為 DbInitializer.cs 的新檔案。
將下列程式碼新增至 Data\DbInitializer.cs:
using ContosoPizza.Models; namespace ContosoPizza.Data { public static class DbInitializer { public static void Initialize(PizzaContext context) { if (context.Pizzas.Any() && context.Toppings.Any() && context.Sauces.Any()) { return; // DB has been seeded } var pepperoniTopping = new Topping { Name = "Pepperoni", Calories = 130 }; var sausageTopping = new Topping { Name = "Sausage", Calories = 100 }; var hamTopping = new Topping { Name = "Ham", Calories = 70 }; var chickenTopping = new Topping { Name = "Chicken", Calories = 50 }; var pineappleTopping = new Topping { Name = "Pineapple", Calories = 75 }; var tomatoSauce = new Sauce { Name = "Tomato", IsVegan = true }; var alfredoSauce = new Sauce { Name = "Alfredo", IsVegan = false }; var pizzas = new Pizza[] { new Pizza { Name = "Meat Lovers", Sauce = tomatoSauce, Toppings = new List<Topping> { pepperoniTopping, sausageTopping, hamTopping, chickenTopping } }, new Pizza { Name = "Hawaiian", Sauce = tomatoSauce, Toppings = new List<Topping> { pineappleTopping, hamTopping } }, new Pizza { Name="Alfredo Chicken", Sauce = alfredoSauce, Toppings = new List<Topping> { chickenTopping } } }; context.Pizzas.AddRange(pizzas); context.SaveChanges(); } } }
在上述程式碼中:
DbInitializer
類別和Initialize
方法都定義為static
。Initialize
接受將PizzaContext
物件作為參數。- 如果三個資料表中的任何一個資料表都沒有任何記錄,則會建立
Pizza
、Sauce
和Topping
物件。 - 透過使用
AddRange
,將物件Pizza
(及其和Sauce
Topping
瀏覽屬性) 新增至物件圖。 - 透過使用
SaveChanges
將物件圖變更提交至資料庫。
您能夠將 DbInitializer
類別植入資料庫,但必須透過 Program.cs 呼叫此類別。 下列步驟會建立呼叫 DbInitializer.Initialize
的 IHost
擴充方法:
在 [資料] 資料夾中,新增名為 Extensions.cs 的新檔案。
將下列程式碼新增至 Data\Extensions.cs:
namespace ContosoPizza.Data; public static class Extensions { public static void CreateDbIfNotExists(this IHost host) { { using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; var context = services.GetRequiredService<PizzaContext>(); context.Database.EnsureCreated(); DbInitializer.Initialize(context); } } } }
在上述程式碼中:
CreateDbIfNotExists
方法會定義為IHost
的擴充。已建立對
PizzaContext
服務的參考。EnsureCreated 可確保資料庫存在。
重要
如果資料庫不存在,則
EnsureCreated
會建立新的資料庫。 未將新資料庫設定為進行移轉,因此使用此方法時請小心。即會呼叫
DbIntializer.Initialize
方法。PizzaContext
物件會以參數的形式傳遞。
最後,在 Program.cs 中,以下列程式碼取代
// Add the CreateDbIfNotExists method call
註解,以呼叫新的擴充方法:app.CreateDbIfNotExists();
此程式碼會呼叫您稍早在每次應用程式執行時定義的擴充方法。
儲存您所有的變更並執行
dotnet build
。
您已撰寫執行基本 CRUD 作業所需的所有程式碼,並在啟動時植入資料庫。 在下一個練習中,您會在應用程式中測試那些作業。