Truy vấn với EF Core
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 5.Tóm tắt
Database đã có bảng. Giờ API cần đọc, lọc, thêm, sửa, xoá sản phẩm thật. Với EF Core, bạn viết LINQ như với list trong bộ nhớ, còn EF Core dịch nó sang SQL và chạy trong database.
Khái niệm
🔍 Truy vấn trên DbSet: LINQ viết trên DbSet không chạy trong bộ nhớ, mà được EF Core dịch sang câu SQL và chạy trong database.
💾 SaveChangesAsync: method ghi mọi thay đổi (thêm, sửa, xoá) mà DbContext đang theo dõi xuống database trong một lần.
| Việc | Code |
|---|---|
| Đọc hết | await _db.Products.ToListAsync() |
| Lọc | await _db.Products.Where(...).ToListAsync() |
| Tìm theo khoá | await _db.Products.FindAsync(id) |
| Thêm | _db.Products.Add(p) rồi SaveChangesAsync() |
| Sửa | đổi property rồi SaveChangesAsync() |
| Xoá | _db.Products.Remove(p) rồi SaveChangesAsync() |
Ví dụ
Phần đọc ghi database nằm trong DbProductStore, sau interface IProductStore
như bài Dependency injection. Controller không biết có EF Core:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
public interface IProductStore
{
Task<List<Product>> InStockAsync();
Task<bool> SetStockAsync(int id, int stock);
}
public class DbProductStore : IProductStore
{
private readonly ShopDbContext _db;
public DbProductStore(ShopDbContext db)
{
_db = db;
}
public async Task<List<Product>> InStockAsync() =>
await _db.Products
.Where(p => p.Stock > 0)
.ToListAsync();
public async Task<bool> SetStockAsync(
int id, int stock)
{
var product = await _db.Products.FindAsync(id);
if (product == null)
{
return false;
}
product.Stock = stock;
await _db.SaveChangesAsync();
return true;
}
}
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly IProductStore _store;
public ProductsController(IProductStore store)
{
_store = store;
}
[HttpGet("in-stock")]
public async Task<List<Product>> InStock() =>
await _store.InStockAsync();
[HttpPut("{id}/stock")]
public async Task<IActionResult> SetStock(
int id, int stock)
{
if (!await _store.SetStockAsync(id, stock))
{
return NotFound();
}
return NoContent();
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int Stock { get; set; }
}
public class ShopDbContext : DbContext
{
public ShopDbContext(
DbContextOptions<ShopDbContext> options)
: base(options)
{
}
public DbSet<Product> Products => Set<Product>();
}- Đăng ký trong
Program.cs:builder.Services.AddScoped<IProductStore, DbProductStore>();. Phải làScopedvìDbContextlàScoped, để tránh lỗi "Singleton phụ thuộc Scoped" ở bài Dependency injection. Where(...).ToListAsync(): điều kiện lọc chạy trong database, chỉ các dòng khớp mới được gửi về.FindAsync(id)tìm theo khoá chính, không có thì trảnull.- Sửa: chỉ cần đổi property rồi gọi
SaveChangesAsync.DbContexttự biết object nào đã đổi và sinh câuUPDATE. SaveChangesAsyncgói mọi câuINSERT,UPDATE,DELETEvào một transaction như bài Transaction của khoá SQL: một câu lỗi thì không câu nào được lưu.- Mọi method đọc ghi database đều có bản
Async, dùng kèmawait.
Thử ngay
Bảng PRODUCTS vẫn còn trống. Trong VS Code, kết nối Oracle bằng user
shopapi, mật khẩu shopapi_pw, service name FREEPDB1, rồi thêm ba sản
phẩm mẫu mà các bài sau dùng lại:
INSERT INTO PRODUCTS (NAME, PRICE, STOCK)
VALUES ('Bút bi', 5000, 120);
INSERT INTO PRODUCTS (NAME, PRICE, STOCK)
VALUES ('Vở', 12000, 0);
INSERT INTO PRODUCTS (NAME, PRICE, STOCK)
VALUES ('Thước', 7000, 50);
COMMIT;Để xem câu SQL mà EF Core sinh ra, thêm vào mục Logging:LogLevel của
appsettings.Development.json:
"Microsoft.EntityFrameworkCore.Database.Command": "Information"Chạy server, gọi curl -i http://localhost:5000/api/products/in-stock rồi
xem cửa sổ đang chạy server.
Đoán trước khi chạy: điều kiện Stock > 0 được lọc trong C# sau khi đọc
hết bảng, hay nằm ngay trong câu SQL?
Xem kết quả
SELECT "p"."ID", "p"."NAME", "p"."PRICE", "p"."STOCK"
FROM "PRODUCTS" "p"
WHERE "p"."STOCK" > 0Nằm trong câu SQL. EF Core dịch lambda p => p.Stock > 0 thành
WHERE "p"."STOCK" > 0, nên Oracle chỉ trả về các dòng còn hàng. Response
là 200 kèm hai sản phẩm Bút bi và Thước, không có Vở.
Lỗi hay gặp
Gọi ToListAsync() trước khi lọc. Cả bảng được đọc về bộ nhớ rồi mới lọc.
Bảng có một triệu dòng thì cả một triệu dòng bị đọc vào bộ nhớ.
// SAI — đọc hết bảng rồi mới lọc trong C#
var all = await _db.Products.ToListAsync();
var inStock = all.Where(p => p.Stock > 0).ToList();// ĐÚNG — lọc trong database
var inStock = await _db.Products
.Where(p => p.Stock > 0)
.ToListAsync();Quên SaveChangesAsync(). Add, Remove hay đổi property chỉ được ghi
nhận trong DbContext. Không gọi SaveChangesAsync thì không có gì được
ghi xuống database.
Tóm tắt
- LINQ trên
DbSetđược dịch sang SQL và chạy trong database. - Lọc bằng
Wheretrước, gọiToListAsyncsau cùng. - Thêm, sửa, xoá xong phải gọi
SaveChangesAsyncmới được ghi. - Dùng bản
Asynckèmawaitcho mọi thao tác với database.
Bình luận
Đang tải bình luận…