練習 - 與資料互動

已完成

在上一個練習中,您已建立實體類別和資料庫內容。 您接著使用 EF Core 移轉,來建立資料庫結構描述。

在此練習中,您會完成 PizzaService 實作。 此服務會使用 EF Core,在資料庫上執行 CRUD 作業。

撰寫 CRUD 作業的程式碼

若要完成 PizzaService 實作,請在 Services\PizzaService.cs 中完成下列步驟:

  1. 如此範例中所示進行以下變更:

    1. 新增 using ContosoPizza.Data; 指示詞。
    2. 新增 using Microsoft.EntityFrameworkCore; 指示詞。
    3. 在建構函式之前為 PizzaContext 新增類別層級的欄位。
    4. 變更建構函式方法簽章以接受 PizzaContext 參數。
    5. 變更建構函式方法程式碼,將參數指派給此欄位。
    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 插入建構函式中。

  2. 以下列程式碼取代 GetAll 方法:

    public IEnumerable<Pizza> GetAll()
    {
        return _context.Pizzas
            .AsNoTracking()
            .ToList();
    }
    

    在上述程式碼中:

    • Pizzas 集合包含披薩資料表中的所有資料列。
    • AsNoTracking 擴充方法會指示 EF Core 停用變更追蹤。 此為唯讀操作,因此 AsNoTracking 可以最佳化效能。
    • 都會以 ToList 傳回所有披薩。
  3. 以下列程式碼取代 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 運算式,透過使用積極式載入,指定在結果中包含 ToppingsSauce 瀏覽屬性。 如果缺少此運算式,EF Core 會針對這些屬性傳回 null。
    • SingleOrDefault 方法會傳回符合 Lambda 運算式的披薩。
      • 如果沒有相符的記錄,則會傳回 null
      • 如果有多個相符記錄,則會擲回例外狀況。
      • Lambda 運算式會描述 Id 屬性等於 id 參數的記錄。
  4. 以下列程式碼取代 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,將物件變更保存到資料庫。
  5. 以下列程式碼取代 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 建立對現有 PizzaSauce 物件的參考。 Find 是經過最佳化的方法,可依主索引鍵查詢記錄。 Find 在查詢資料庫前,會先搜尋本地實體圖。
    • 會將 Pizza.Sauce 屬性設定為 Sauce 物件。
    • EF Core 偵測到您在 Pizza 上設定 Sauce 屬性,因此無須使用 Update 方法呼叫。
    • SaveChanges 方法會指示 EF Core,將物件變更保存到資料庫。
  6. 以下列程式碼取代 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 建立對現有 PizzaTopping 物件的參考。
    • 會使用 .Add 方法,將 Topping 物件新增至 Pizza.Toppings 集合。 如果新的集合不存在,則會建立新的集合。
    • SaveChanges 方法會指示 EF Core,將物件變更保存到資料庫。
  7. 以下列程式碼取代 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,將物件變更保存到資料庫。
  8. 儲存您所有的變更並執行 dotnet build。 修正發生的任何錯誤。

植入資料庫

您已為 PizzaService 撰寫 CRUD 作業的程式碼,但如果資料庫包含良好的資料,則測試讀取作業會比較容易。 您決定修改應用程式,以在啟動時植入資料庫。

警告

此資料庫植入程式碼沒有考慮到競爭條件,在不影響變更的情況下,於分散式環境中使用時請小心。

  1. 在 [資料] 資料夾中,請新增名為 DbInitializer.cs 的新檔案。

  2. 將下列程式碼新增至 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 物件作為參數。
    • 如果三個資料表中的任何一個資料表都沒有任何記錄,則會建立 PizzaSauceTopping 物件。
    • 透過使用 AddRange,將物件 Pizza (及其和 SauceTopping 瀏覽屬性) 新增至物件圖。
    • 透過使用 SaveChanges 將物件圖變更提交至資料庫。

您能夠將 DbInitializer 類別植入資料庫,但必須透過 Program.cs 呼叫此類別。 下列步驟會建立呼叫 DbInitializer.InitializeIHost 擴充方法:

  1. 在 [資料] 資料夾中,新增名為 Extensions.cs 的新檔案。

  2. 將下列程式碼新增至 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 物件會以參數的形式傳遞。

  3. 最後,在 Program.cs 中,以下列程式碼取代 // Add the CreateDbIfNotExists method call 註解,以呼叫新的擴充方法:

    app.CreateDbIfNotExists();
    

    此程式碼會呼叫您稍早在每次應用程式執行時定義的擴充方法。

  4. 儲存您所有的變更並執行 dotnet build

您已撰寫執行基本 CRUD 作業所需的所有程式碼,並在啟動時植入資料庫。 在下一個練習中,您會在應用程式中測試那些作業。

檢定知識

1.

假設您想要寫入一個唯讀查詢。 您該如何向 EF Core 指出不需要物件圖形變更追蹤?