結果導航
本頁包含搜尋結果的兩條導航路(移至定義、在物件總管中選取)與節點位址;排名與 provider 契約見 SQL Search,開出未連線視窗的做法見移至定義。
別台的定義開進未連線的視窗
移至定義把定義開進新查詢視窗,在物件總管中選取把樹展開到那個節點上。前者問 這一筆與查詢視窗是不是同一台(SqlSearchCatalogs.SharesActiveEditorServer):同一台就沿用 那條連線;別台或沒有查詢視窗就開未連線的視窗,開頭兩行註解寫明來源伺服器與資料庫, 第一次 F5 由 SSMS 要求連線。沿用會讓 F5 跑在錯的伺服器上;擋下來則指名別台後只剩一條路。
不拿「範圍選了哪一台」代答:選的十之八九正是查詢視窗那一台。只問伺服器、不問資料庫。
「會不會開未連線的視窗」在查詢視窗換過時整份重算(SqlSearchRow.ObserveActiveEditor,時機見 範圍),不在繪製時問宿主;名稱與提示事先就說「未連線」與 來源。做得到所以不變灰(見清單列),按下去那一刻再比一次為準。
伺服器跟著那一筆走
清單比範圍活得久:換過查詢視窗或指名別台之後,舊列仍來自上一台。 所以 provider 建立命中時就把連著的那一台(SqlSearchOrigin)放進酬載,接線層經 ISqlSearchTarget 讀它,三條路一律照它:
- 物件總管:在樹上找那一台(
FindOnTree),找不到就說找不到,禁止拿任何一台頂替, 包括指名的那一台。 - 移至定義:拿它與查詢視窗比。
- 目錄查詢(父物件、結構、預覽、作業命令):範圍已不在那一台就拒絕並請使用者重搜 (
ResolveOn),不拿範圍那一台的目錄回答。
頂替與代答的症狀一樣:跳到另一台上同名的物件,或同號 object_id 的另一個父物件,而畫面上 看起來完全正常。比對一律走 IsSameServer,伺服器名稱的寫法全專案只有那一份; SERVERPROPERTY('ServerName') 只給人看。
節點位址
由 Metadata/Model/SqlObjectExplorerUrn 組,路徑照 SSMS 自己的 sqlexplorerhier.xml (節點的 Xpath 與 UrnShell)。伺服器那一段沿用物件總管節點自己的 RootUrn,不自己拼 Server[@Name='…']:具名執行個體與有別名的連線上,連線字串裡的名稱與樹上那一行不一樣, 拼出來的位址對不上任何節點。
結構描述缺了就不組位址,不省略那一段——同名不同結構描述在一個資料庫裡是常態,少了它會 停在第一個同名的節點上。三種函式共用 UserDefinedFunction,物件總管沒有把它們分成兩種節點。 資料夾的不變名稱取 UniqueName,沒有時是 XML 上的物件名稱(UserProgrammability、UsrDbFunctions)。
掛在父物件底下的那幾種
觸發程序與條件約束在樹上有自己的節點,只是畫在父物件底下。一筆結果身上只有自己的 object_id,所以位址要先問一趟目錄(SqlMetadataCatalog.GetParentAsync)。那是一條查詢, 不載入父物件的結構:跳到一個條件約束不需要知道那張表的索引與外來鍵長什麼樣子。 父物件是資料表型別時,名稱與結構描述從 sys.table_types 取:sys.objects 上那一列是 TT_… 內部名稱,樹上對不上,連退回父物件都退不到。
| 這一筆是 | 樹上的那一段 |
|---|---|
| 資料行命中 | Column[@Name='…'] |
| 觸發程序 | Trigger[@Name='…'] |
CHECK | Check[@Name='…'] |
| 外來鍵 | ForeignKey[@Name='…'] |
| 主索引鍵、唯一鍵 | Index[@Name='…'] |
DEFAULT | Column[@Name='資料行']/Default[@Name='…'] |
後兩列各有一個容易寫錯的地方。主索引鍵與唯一鍵在「索引鍵」資料夾裡,而它們是索引。 DEFAULT 有三件事都違反直覺:位址掛在資料行底下(那個資料行名稱只有目錄答得出來, 同一條查詢的最後一欄);畫它的卻是資料表的「條件約束」資料夾,所以錨點是那張表 而不是那一行;而且它帶名稱鍵,儘管一個資料行只有一個(SMO 列舉器對具名物件一律組成 父位址/型別[@Name='…'])。三件事各自錯了都很安靜:每次都退到下一個候選, 畫面上選到的是那個資料行,看起來就像功能只做到一半。
資料行命中停在那一行上,不是整個物件——使用者搜的是 CopyNo,樹上就有那一行。 函式例外:樹上只有「參數」資料夾,資料行命中直接停在函式上。移至定義則永遠開整個物件。
導覽服務只認得三種物件
NavigateToUrnAsync 寫死 Databases、UserTables、Views、 UserProgrammability/StoredProcedures,其餘一段只比對父節點的直接子節點。所以 函式、同義字、序列、資料表型別一個都指不到;資料表底下的東西一律停在資料表上; FileTable、圖形資料表(「資料表」的子資料夾)與系統資料庫、快照集(「資料庫」的子資料夾) 裡的一切也指不到。只有使用者先前展開過那一層時才成功。根本原因是資料夾沒有自己的 位址:URN 就是父節點的,ExpandNodeAsync/GetNodeChildrenAsync 也問不到它。
所以每個目標由近到遠給好幾個候選,各帶錨點(AnchorUrn)、沿路資料夾(Folders) 與層數(Depth),都由 SqlObjectExplorerUrn 照樹的形狀給:導覽服務直接指(三種物件)、 錨在資料表上(它底下的東西)、錨在資料庫上、錨在伺服器根上。不先判斷是不是系統 資料庫:樹上那一格看 SMO 的 IsSystemObject,散發資料庫也算,名字看不出來。位址相同的 後幾條成功不算降級。
TrySelectFirstAsync 先導覽到錨點,從 GetSelectedNodes 拿它的 INavigableItem 往下走, 只走進資料夾(Context 與父節點相同;不拿來比候選,否則會把資料夾當父節點選起來)與 位址是候選前綴的物件,把搜尋關在通往候選的路上,找到就 SynchronizeTree。 近的錨點到得了就不再從遠的找:到得了卻找不到代表不在樹上,從遠處只是把同一段重翻。
禁止用 FindNode(節點位址) 一次到底:它從樹根全樹搜尋,每下一層都把沿路資料夾建出來 (向伺服器查詢),沒建過時會翻遍整台伺服器,大的伺服器上是幾分鐘起跳,而 _selecting 讓按鈕從此按不動。FindNode(父物件) 也不用:它列舉的是 WinForms 控制項上的字典。
往下走在背景執行緒上,這是 SSMS 自己的用法:樹展開節點時就在工作執行緒上呼叫 RequestChildren(GetChildren 的本體),同一節點的並行請求併成一趟。放在 UI 執行緒上 才會凍住。深度與時間都要有上限(候選的 Depth、15 秒);逾時放掉的是等待 (WithCancellation),Task.Run 的權杖擋不住已經開跑的 GetChildren。逾時前找到的 候選照樣算數;放掉的那一趟交給 BeginProbe,之後才出的錯才有人接。
資料夾照候選的 Folders(樹上的 InvariantName,不是在地化的顯示文字) 排序再翻:每個沒建過的資料夾是一趟查詢,照樹的順序找函式要先付資料表、檢視好幾趟。 只排序不過濾:名稱對不上時退回樹的順序,過濾的話就是找不到。
同一個錨點只展開、只往下找一次:DEFAULT 與它的資料行一趟一起找,否則資料夾翻兩遍、逾時等兩次。
候選由精確到寬鬆
每一支都交出一串候選,依序試到第一個指得到的為止,並在通知上說出停在哪一層(降級) (「已改為選取資料行 DueDate」):樹上不一定有那個節點(篩選器、版本差異)。只交出最精確的 那一個,條件約束會一律「找不到」;默默當成功則更糟——使用者以為選到條件約束,其實是整張表。
只准由使用者發動
導航會叫出物件總管、搶走焦點,展開節點還要向伺服器問資料。所以這條路徑禁止掛在 選取變更或任何輪詢上——使用者可能正是把物件總管關掉的人。
它也不走 Guard 的收斂:每一種失敗各寫一句在「在物件總管中選取」那一則通知上——樹上沒有這一台、 範圍已換到別台、問不到父物件、節點全都指不到,下一步完全不同。還沒開始就被擋下的也用同一個標題 (Post 成失敗),使用者看到的是同一件事的結果;移至定義同理。
執行緒分工
UI 上挑樹上那一台並解析目錄(SqlSearchCatalogs 的解析都是 UI 親和的)→ 背景問父物件(只有觸發 程序與條件約束)→ UI 開始導航、背景從錨點往下找、回 UI 收掉通知。GetParentAsync 自己就是 Task.Run, 接線層不再包一次。
UI 親和性由被呼叫的那一端自保(規則與理由見平台護欄): SsmsObjectExplorer.TrySelectFirstAsync 進場就 SwitchToMainThreadAsync。改成回傳前才切回去 的症狀在這條路徑上最難看出來:資料表與檢視同步算完候選、續程原地跑,完全正常;只有中間真的 await 過的條件約束與觸發程序丟 must be called on the UI thread。