ASP.NET Core Web API With EF Core
2024-06-20
筆記 Web API 專案搭配 EF Core 使用的注意要點。
Basic Entity
[Route("RPS")]
[HttpGet]
public async Task<ActionResult<string>> GetOneRPS(string user)
{
return GetRPSResult(user);
}
Collection
// GET: api/One/RPSMultitimes
[Route("RPSMultitimes")]
[HttpPost]
public async Task<ActionResult<List<string>>> GetOneRPSMultitimes(string[] users)
{
var repsonses = new List<string> { };
foreach (string user in users)
{
string user_copy = user;
repsonses.Add(GetRPSResult(user).Value);
}
return repsonses;
}
One to Many
Model (One to Many)
Customer 有零個到多個 Order 且 Order 必須要有 1 個 Customer
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
public ICollection<Order>? Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public string Product { get; set; }
public int CustomerId { get; set; }
public Customer? Customer { get; set; }
}
GET Series
預設的 Get 方式不會將 Dependent Entity 一起載入,並需要主動調整處理邏輯,加入 include
才能夠 eager loading 預先載入 Dependenty Entity。
[HttpGet]
public async Task<ActionResult<IEnumerable<Customer>>> GetCustomer()
{
return await _context.Customer.Include(c => c.Orders).ToListAsync();
}
GET https://localhost:7001/api/Customers
POST Series
_context.Customers.Add(customer);
await _context.SaveChangesAsync();
一對多新增單一實體
POST https://localhost:7052/api/Customers
Content-Type: application/json
{
"name": "Webber"
}
一對多新增所有實體
POST https://localhost:7052/api/Customers
Content-Type: application/json
{
"name": "Webber",
"orders": [
{
"product": "Pear"
},
{
"product": "Lemon"
},
{
"product": "Orange"
}
]
}
PUT Series
Scaffold 預設提供的處理邏輯只能夠針對主體 Entity 如果要對於 Navigation Property (Many 的 Entity Order) 也進行作業,需要加上額外的處理邏輯。
_context.Entry(customer).State = EntityState.Modified;
// check if customer has orders
if (customer.Orders != null)
{
foreach (var order in customer.Orders)
{
if (order.OrderId == 0)
{
// 新增額外關聯實體
_context.Orders.Add(order);
}
else
{
// 更新所有實體
_context.Entry(order).State = EntityState.Modified;
}
}
}
新增關聯實體的方式 (Order)
PUT https://localhost:7052/api/Customers/18
Content-Type: application/json
{
"customerId": 18,
"name": "Webber",
"orders": [
{
"product": "Corn"
}
]
}
更新所有實體的方式 (Customer & Order)
PUT https://localhost:7052/api/Customers/1
Content-Type: application/json
{
"customerId": 1,
"name": "Webber",
"orders": [
{
"OrderId": 3,
"product": "Pear"
}
]
}
PUT 預設的操作無法刪除,但如果是要藉由 PUT 更新關聯實體的時候先刪除所有的關係,再加入新關聯實體,可以將處理邏輯進行以下處理:
_context.Entry(customer).State = EntityState.Modified;
// check if customer has orders
if (customer.Orders != null)
{
_context.Orders.RemoveRange(_context.Orders.Where(o => o.CustomerId == id));
// Remove 後要將異動儲存回資料庫,同步 Entity 與 資料庫的一致
await _context.SaveChangesAsync();
foreach (var order in customer.Orders)
{
if (order.OrderId == 0)
{
_context.Orders.Add(order);
}
else
{
_context.Entry(order).State = EntityState.Modified;
}
}
}
DELETE Series
如果刪除主 Entity 因為有 CASCADE ON DELETE 會自動刪除關聯實體相當方便。
One to Many (Self Reference)
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int? ManagerId { get; set; }
public Employee? Manager { get; set; }
public ICollection<Employee> Members { get; } = new List<Employee>();
}
POST Series
同時新增 Entity 與參照的 Entity
POST https://localhost:7052/api/Employees
Content-Type: application/json
{
"name": "John",
"manager": {
"name": "Boss"
}
}
新增 Entity 並指定已存在的 Entity 為參照
POST https://localhost:7052/api/Employees
Content-Type: application/json
{
"name": "Allen",
"managerId": 6
}
PUT Series
PUT https://localhost:7052/api/Employees/5
Content-Type: application/json
{
"id": 5,
"name": "Webber",
"managerId": 6
}
DELETE Series
刪除的情境,如果直接刪除被參照的 Entity,會受到 Foreign Key 的 REFERENCE constraint。
必須依序刪除,可以使用簡單的 DFS 方式來進行處理,但處理邏輯仍是先將負責標註為 Deleted,最後透過 SaveChangesAsync
才交由 EF Core 來判斷刪除順序。
var entityToDelete = new Stack<Employee>();
entityToDelete.Push(employee);
while (entityToDelete.Count > 0)
{
var current = entityToDelete.Pop();
await _context.Entry(current).Collection(c => c.Members).LoadAsync();
foreach (var child in current.Members)
{
entityToDelete.Push(child);
}
_context.Employees.Remove(current);
}
await _context.SaveChangesAsync();
Many to Many
Model (Many to Many)
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public ICollection<StudentCourse>? StudentCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public ICollection<StudentCourse>? StudentCourses { get; set; }
}
public class StudentCourse
{
public int StudentId { get; set; }
public Student? Student { get; set; }
public int CourseId { get; set; }
public Course? Course { get; set; }
public string? Grade { get; set; }
}
Post Series
POST Method
[HttpPost]
public async Task<ActionResult<Student>> PostStudent(Student student)
{
_context.Student.Add(student);
await _context.SaveChangesAsync();
return CreatedAtAction("GetStudent", new { id = student.StudentId }, student);
}
多對多新增單一實體
新增單一實體 (Student)
POST https://localhost:7052/api/Students/
Content-Type: application/json
{
"name": "Lynn"
}
多對多新增單一實體與使用既有實體
新增單一實體 (Student) 與使用既有實體 (Coruse)
POST https://localhost:7052/api/Students/
Content-Type: application/json
{
"name": "Marx",
"studentCourses": [
{
"courseId": 3,
"grade": "A"
}
]
}
多對多直接新增所有實體
POST https://localhost:7052/api/Students/
Content-Type: application/json
{
"name": "Marx",
"studentCourses": [
{
"course": {
"title": "Python"
},
"grade": "A"
}
]
}
PUT Series
Scaffold 提供的方法是透過 EntityState.Modified
來達到更新的效果,但僅限於 Student Entity,Navigation Property 如 StudentCourse 並不會隨著被修改。
_context.Entry(student).State = EntityState.Modified;
await _context.SaveChangesAsync();
如果要修改 Navigation Property 必須主動進行相關的程式碼處理:
_context.Entry(student).State = EntityState.Modified;
foreach (var studentCourse in student.StudentCourses)
{
// Add Entity Situation
if (studentCourse.CourseId == 0)
{
_context.Course.Add(studentCourse.Course);
_context.Entry(studentCourse).State = EntityState.Added;
}
// Modify Entity Situation
else
{
_context.Entry(studentCourse).State = EntityState.Modified;
}
}
http request 達成調整 Join Table 的 Property
PUT https://localhost:7052/api/Students/3
Content-Type: application/json
{
"studentId": 3,
"name": "Webber",
"studentCourses": [
{
"studentId": 3,
"courseId": 1,
"grade": "D"
}
]
}
第二個 http request 達成新增 Course Entity 及 Join Table Entity
###
PUT https://localhost:7052/api/Students/3
Content-Type: application/json
{
"studentId": 3,
"name": "Webber",
"studentCourses": [
{
"course": {
"title": "Chemistry"
},
"grade": "E"
}
]
}
DELETE Series
Scaffold 預設提供的刪除方式。
public async Task<IActionResult> DeleteStudent(int id)
{
var student = await _context.Student.FindAsync(id);
if (student == null)
{
return NotFound();
}
_context.Student.Remove(student);
await _context.SaveChangesAsync();
return NoContent();
}
DELETE https://localhost:7052/api/Students/3
預設只支援單一 Entity 刪除,如果想要全部刪除,可以調整 route 以及當沒有輸入 id 情況下的處理邏輯為以下方式:
[HttpDelete("{id?}")]
public async Task<IActionResult> DeleteStudent(int id)
{
if (id == 0)
{
_context.Student.RemoveRange(_context.Student);
await _context.SaveChangesAsync();
return NoContent();
}
...
}