上一篇整理完 Controller 和 Service 的分工後,我開始比較能看懂這種程式:
public IActionResult Detail(int id)
{
var model = _userService.GetDetail(id);
return View(model);
}
以前看到這段,我會有一個很直接的疑問:
就這樣?
資料到底在哪裡查?
SQL 寫在哪?
GetDetail() 裡面又在幹嘛?
因為 Controller 裡只看到:
_userService.GetDetail(id);
真正的處理邏輯根本不在這裡。
這時候我才開始真的用到 Visual Studio 很實用的一個功能:
F12
也就是:
Go To Definition
移至定義
這篇就來實際追一次:
Controller
↓
Service
↓
Repository / DBService / DbContext
↓
SQL
↓
Database
看看一筆資料到底是怎麼一路查出來的。
假設目前 Controller 是:
public class UserController : Controller
{
private readonly IUserService _userService;
public UserController(IUserService userService)
{
_userService = userService;
}
public IActionResult Detail(int id)
{
var model = _userService.GetDetail(id);
return View(model);
}
}
這時我最想知道的是:
GetDetail(id)
到底寫在哪裡。
我會把游標放在:
GetDetail
上面。
然後按:
F12
按下 F12 之後,不一定直接看到真正邏輯。
有時候會先跳到:
public interface IUserService
{
UserDetailViewModel GetDetail(int id);
}
我第一次看到這裡會有點傻眼。
因為:
蛤?
怎麼只有一行?
真正程式呢?
這是因為:
IUserService
只是 Interface。
它主要在說:
這個 Service 應該提供 GetDetail() 這個功能
但不負責寫:
GetDetail() 到底怎麼做
可以先把它理解成:
IUserService
定義功能規格
UserService
真正實作功能
所以看到:
UserDetailViewModel GetDetail(int id);
沒有 { } 裡面的程式,不代表追錯了。
只是還沒追到底。
假設現在停在:
public interface IUserService
{
UserDetailViewModel GetDetail(int id);
}
接著要找:
誰實作了 IUserService?
可以在:
IUserService
上使用:
F12
有時候可以透過 IDE 導航找到實作。
也可以使用:
尋找所有參考
或 Visual Studio 的:
Go To Implementation
來找實作類別。
最後可能找到:
public class UserService : IUserService
{
public UserDetailViewModel GetDetail(int id)
{
...
}
}
這裡才是真正開始處理資料的地方。
例如:
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public UserDetailViewModel GetDetail(int id)
{
var user = _userRepository.GetById(id);
var model = new UserDetailViewModel
{
Name = user.Name,
Email = user.Email
};
return model;
}
}
這裡可以先拆成:
_userRepository.GetById(id)
↓
去取得資料
user
↓
拿到資料庫相關物件
UserDetailViewModel
↓
整理成畫面需要的資料
return model
↓
回給 Controller
所以 Service 裡不一定自己直接寫 SQL。
它可能又把資料存取交給:
Repository
DBService
DbContext
我看到:
_userRepository.GetById(id);
就可以繼續:
游標放在 GetById
↓
F12
可能又先跳到:
public interface IUserRepository
{
User GetById(int id);
}
再找到真正實作:
public class UserRepository : IUserRepository
{
public User GetById(int id)
{
...
}
}
這時整條追蹤線就變成:
UserController
↓
IUserService
↓
UserService
↓
IUserRepository
↓
UserRepository
第一次看到企業專案一直:
Ixxx
xxxService
IxxxRepository
xxxRepository
真的會覺得繞超遠。
但其實每一層都有自己的責任。
如果專案使用 Entity Framework,可能看到:
public User GetById(int id)
{
return _dbContext.Users
.FirstOrDefault(x => x.Id == id);
}
這時就已經接近資料庫了。
可以拆成:
_dbContext.Users
↓
Users 這組資料
FirstOrDefault(...)
↓
找符合條件的第一筆
x.Id == id
↓
查詢條件
整條線就是:
Controller 傳入 id
↓
Service
↓
Repository
↓
DbContext
↓
Users
↓
依 id 查詢
↓
Database
不是每個企業專案都用 Entity Framework。
有些專案可能會看到:
public UserDto GetById(int id)
{
string sql = @"
SELECT Id, Name, Email
FROM Users
WHERE Id = @Id
";
return _dbService.QueryFirstOrDefault<UserDto>(
sql,
new { Id = id }
);
}
這時候就更直接。
可以看到:
SELECT
↓
查哪些欄位
FROM
↓
查哪張表
WHERE
↓
查詢條件
@Id
↓
傳入的參數
所以不同專案可能長得不一樣。
但追法其實一樣:
Controller
↓
Service
↓
下一個被呼叫的方法
↓
再 F12
↓
直到找到真正資料來源
有些企業專案會把 SQL 寫在資料庫 Stored Procedure 裡。
程式可能只有:
return _dbService.Query<UserDto>(
"usp_GetUserDetail",
parameters
);
這時程式碼裡看不到完整 SQL。
那就要繼續確認:
usp_GetUserDetail
是不是資料庫裡的 Stored Procedure。
所以:
追到 DBService
不一定代表已經看到 SQL。
有時候真正查詢還藏在資料庫裡。
我後來發現 F12 真正有用的地方,不是背快捷鍵。
而是建立一個習慣:
這個方法是誰呼叫的?
這個方法真正寫在哪?
裡面又呼叫了誰?
資料到底在哪一層拿到?
例如:
var model = _userService.GetDetail(id);
我會一路拆:
GetDetail()
↓
UserService.GetDetail()
裡面呼叫:
GetById()
↓
UserRepository.GetById()
裡面呼叫:
DbContext / SQL
↓
Database
這樣一個原本完全不知道資料從哪裡來的方法,就開始被拆開。
這個超實用。
因為一直 F12 之後,很容易變成:
Controller
↓
Service
↓
Repository
↓
DbContext
↓
某個 Model
↓
另一個 Method
然後:
我剛剛到底從哪裡來的?
這時可以使用:
Ctrl + -
回到上一個瀏覽位置。
例如:
Controller
↓ F12
Service
↓ F12
Repository
想回 Service:
Ctrl + -
再按一次:
Ctrl + -
回 Controller。
這對追大型專案真的很好用。
F12 是:
這個東西定義在哪?
而:
Shift + F12
比較像:
哪些地方有使用這個東西?
例如我找到:
public UserDetailViewModel GetDetail(int id)
想知道:
這個方法還有哪些地方在用?
就可以找:
Find All References
尋找所有參考
這時可能看到:
UserController
AdminController
ReportService
都有呼叫它。
這個功能在準備修改共用方法前特別重要。
因為如果我只看到:
這個方法現在這一頁會用
就直接改。
結果它其實有五個地方都在用,就可能一起被影響。
所以我現在會記:
F12
往裡面追
Shift + F12
往外看誰在用
Visual Studio 還有:
Alt + F12
Peek Definition
它可以在目前畫面直接開一個小視窗查看定義。
不用整個跳走。
例如我只是想快速確認:
UserDetailViewModel 裡有哪些欄位?
可以:
Alt + F12
看一下。
看完直接關掉。
如果只是短暫確認類別內容,會比一直跳檔案方便。
我目前會先把幾個功能記成:
F12
移至定義
Alt + F12
快速偷看定義
Shift + F12
找誰在使用
Ctrl + -
回上一個位置
不用一次背很多 Visual Studio 快捷鍵。
這四個已經很夠用。
前面一路往下追:
Controller
↓
Service
↓
Repository
↓
Database
但查完之後,資料還要一路回來。
例如 Repository:
public User GetById(int id)
{
return _dbContext.Users
.FirstOrDefault(x => x.Id == id);
}
回傳:
User Entity
Service 收到:
var user = _userRepository.GetById(id);
再整理:
var model = new UserDetailViewModel
{
Name = user.Name,
Email = user.Email
};
回 Controller:
var model = _userService.GetDetail(id);
最後:
return View(model);
View:
@model UserDetailViewModel
<h1>@Model.Name</h1>
<p>@Model.Email</p>
所以完整流程不是只有:
Controller
↓
Database
而是:
Request
↓
Controller
↓
Service
↓
Repository / DBService
↓
Database
資料查回來
↓
Entity / DTO
↓
Service
↓
ViewModel
↓
Controller
↓
View
↓
HTML
這就是 F12 真正開始有價值的地方。
假設畫面顯示:
部門名稱是空的
我可以先看 View:
@Model.DepartmentName
接著往回:
View
↓
UserDetailViewModel.DepartmentName
↓
Controller 的 return View(model)
↓
model 從哪裡來?
↓
_userService.GetDetail(id)
↓
F12
進 Service 後看:
DepartmentName = user.DepartmentName
再往下追:
user.DepartmentName 從哪裡來?
↓
Repository
↓
SQL / DbContext
最後就能判斷:
資料庫本來就沒有?
SQL 沒查 DepartmentName?
DTO 沒接到?
Service 轉 ViewModel 時漏掉?
Controller 傳錯 Model?
View 用錯欄位?
這比:
畫面空的
↓
直接在 View 亂改
有效很多。
null 就直接怪資料庫這也是我後來會特別注意的。
例如:
@Model.DepartmentName
顯示空白。
不代表:
Database 沒資料
資料有可能在 Repository 還正常:
DepartmentName = "資訊部"
但 Service 轉換時漏掉:
var model = new UserDetailViewModel
{
Name = user.Name,
Email = user.Email
// 忘記 DepartmentName
};
最後 View 當然拿不到。
所以資料流裡任何一段都可能斷:
Database
↓
Repository
↓
Entity / DTO
↓
Service
↓
ViewModel
↓
Controller
↓
View
這也是為什麼後面要學中斷點。
F12 可以告訴我:
程式寫在哪裡
但它不能告訴我:
執行時資料現在到底是多少
假設 Controller 有:
var model = _userService.GetDetail(id);
我會照這個順序:
第一步
F12 GetDetail
↓
看看先到 Interface 還是實作
第二步
找到真正的 UserService.GetDetail()
↓
看它裡面又呼叫哪個方法
第三步
F12 Repository / DBService 方法
↓
找真正資料存取位置
第四步
確認查詢條件
↓
id 是不是一路傳下來?
第五步
確認查詢結果用什麼 Entity / DTO 接
第六步
回 Service
↓
看怎麼轉成 ViewModel
第七步
回 Controller
↓
確認 return View(model)
第八步
回 View
↓
確認 @Model 使用哪個欄位
如果途中跳太遠:
Ctrl + -
往回。
如果準備修改某個共用方法:
Shift + F12
先確認誰還在用。
這就是我現在追企業專案最常用的一條路。
這次我主要問:
Controller 裡只看到
_userService.GetDetail(id),真正程式在哪裡?
F12 為什麼有時候只跳到 Interface?
Interface 和真正的 Service 實作要怎麼找到?
Service 裡又呼叫 Repository 時要繼續怎麼追?
Entity Framework、SQL、Stored Procedure 的專案,最後資料可能各自藏在哪裡?
AI 幫我先整理出:
Controller
↓
Interface
↓
Service
↓
Repository / DBService
↓
Database
但真正有幫助的是,我回到 Visual Studio 自己一路按 F12。
因為每個企業專案的分層都不一定一樣。
有些是:
Controller
↓
Service
↓
DbContext
有些是:
Controller
↓
Service
↓
Repository
↓
DbContext
也有可能:
Controller
↓
Service
↓
DBService
↓
SQL / Stored Procedure
AI 可以告訴我可能的方向。
但最後還是要以:
現在這個專案實際怎麼寫
為準。
F12 最基本的用途是:
這個方法到底定義在哪裡?
但真正追企業專案時,我會把它當成:
沿著程式呼叫一路往下走
例如:
Controller
↓
Service Interface
↓
Service
↓
Repository / DBService
↓
DbContext / SQL
↓
Database
常用的幾個功能:
F12
移至定義
Alt + F12
快速查看定義
Shift + F12
尋找所有參考
Ctrl + -
回上一個位置
而查詢資料的完整方向可以先記成:
Controller
↓
Service
↓
資料存取層
↓
Database
↓
Entity / DTO
↓
Service 轉換
↓
ViewModel
↓
Controller
↓
View
所以現在看到:
_userService.GetDetail(id);
不會再覺得:
真正的程式是不是消失了?
而是知道:
游標放上去
↓
F12
↓
繼續追
不過 F12 只能告訴我:
程式碼寫在哪裡
還有一個更重要的問題:
執行的時候,
它真的有跑到這一行嗎?
下一篇就來用中斷點確認:
我找到程式碼了,但怎麼知道實際執行時真的有跑進這個方法?