@elf-express/admin-net-mcp 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/index.js +83 -88
  2. package/knowledge/Furion_Teaching_Manual/04-1-/351/205/215/347/275/256.md +442 -0
  3. package/knowledge/Furion_Teaching_Manual/04-2-/351/201/270/351/240/205.md +363 -0
  4. package/knowledge/Furion_Teaching_Manual/05-1-/345/213/225/346/205/213WebAPI.md +825 -0
  5. package/knowledge/Furion_Teaching_Manual/05-2-HttpContext.md +217 -0
  6. package/knowledge/Furion_Teaching_Manual/05-3-/347/257/251/351/201/270/345/231/250/346/224/224/346/210/252/345/231/250AOP.md +581 -0
  7. package/knowledge/Furion_Teaching_Manual/05-4-/350/253/213/346/261/202/347/250/275/346/240/270/346/227/245/350/252/214.md +129 -0
  8. package/knowledge/Furion_Teaching_Manual/05-5-/344/270/255/344/273/213/350/273/237/351/253/224Middleware.md +328 -0
  9. package/knowledge/Furion_Teaching_Manual/05-6-Vue-React-Angular/344/273/213/351/235/242/344/273/243/347/220/206.md +317 -0
  10. package/knowledge/Furion_Teaching_Manual/06-1/350/246/217/347/257/204/345/214/226/346/216/245/345/217/243.md +1458 -0
  11. package/knowledge/Furion_Teaching_Manual/06-2/347/254/254/344/270/211/346/226/271API_Scalar.md +91 -0
  12. package/knowledge/Furion_Teaching_Manual/07-/345/217/213/345/245/275/344/276/213/345/244/226/350/231/225/347/220/206.md +511 -0
  13. package/knowledge/Furion_Teaching_Manual/08-1-/350/263/207/346/226/231/351/251/227/350/255/211/345/237/272/347/244/216/344/275/277/347/224/250.md +587 -0
  14. package/knowledge/Furion_Teaching_Manual/10-1-SqlSugar/346/225/264/345/220/210.md +336 -0
  15. package/knowledge/Furion_Teaching_Manual/11-SaaS /345/244/232/347/247/237/346/210/266/347/255/206/350/250/230.md" +271 -0
  16. package/knowledge/Furion_Teaching_Manual/12-furion-dependency-injection.md +408 -0
  17. package/knowledge/Furion_Teaching_Manual/13-/347/211/251/344/273/266/350/263/207/346/226/231/346/230/240/345/260/204/357/274/210Mapster/357/274/211.md +162 -0
  18. package/knowledge/Furion_Teaching_Manual/14-/345/210/206/345/270/203/345/274/217/347/274/223/345/255/230.md +311 -0
  19. package/knowledge/Furion_Teaching_Manual/15-/345/256/211/345/205/250/351/211/264/346/235/203.md +832 -0
  20. package/knowledge/Furion_Teaching_Manual/17-/346/252/242/350/246/226/347/257/204/346/234/254/345/274/225/346/223/216.md +327 -0
  21. package/knowledge/Furion_Teaching_Manual/18-/346/227/245/350/252/214/350/250/230/351/214/204.md +639 -0
  22. package/knowledge/Furion_Teaching_Manual/19-1-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/237/272/347/244/216/344/275/277/347/224/250.md +621 -0
  23. package/knowledge/Furion_Teaching_Manual/19-2-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/351/200/262/351/232/216/346/214/207/345/215/227.md +928 -0
  24. package/knowledge/Furion_Teaching_Manual/19-3-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/270/270/350/246/213/345/225/217/351/241/214.md +362 -0
  25. package/knowledge/Furion_Teaching_Manual/20-/350/263/207/346/226/231/345/212/240/350/247/243/345/257/206.md +286 -0
  26. package/knowledge/Furion_Teaching_Manual/20-/351/231/204/351/214/204-KSort/350/263/207/346/226/231/347/260/275/345/220/215/345/256/214/346/225/264/345/216/237/345/247/213/347/242/274.md +305 -0
  27. package/knowledge/Furion_Teaching_Manual/21-/345/205/250/347/220/203/345/214/226/345/222/214/346/234/254/345/234/260/345/214/226.md +342 -0
  28. package/knowledge/Furion_Teaching_Manual/22-/344/272/213/344/273/266/345/214/257/346/265/201/346/216/222EventBus.md +448 -0
  29. package/knowledge/Furion_Teaching_Manual/23-JSON/345/272/217/345/210/227/345/214/226.md +340 -0
  30. package/knowledge/Furion_Teaching_Manual/24-/345/215/263/346/231/202/351/200/232/350/250/212SignalR.md +247 -0
  31. package/knowledge/Furion_Teaching_Manual/25-/350/274/224/345/212/251/350/247/222/350/211/262/346/234/215/345/213/231WorkerService.md +295 -0
  32. package/knowledge/Furion_Teaching_Manual/26-1-/346/216/222/347/250/213/344/275/234/346/245/255/345/256/232/346/231/202/344/273/273/345/213/231.md +631 -0
  33. package/knowledge/Furion_Teaching_Manual/26-2-Cron/350/241/250/351/201/224/345/274/217.md +203 -0
  34. package/knowledge/Furion_Teaching_Manual/26-3-/344/273/273/345/213/231/344/275/207/345/210/227TaskQueue.md +215 -0
  35. package/knowledge/Furion_Teaching_Manual/27-/345/210/206/346/225/243/345/274/217ID/347/224/237/346/210/220.md +86 -0
  36. package/knowledge/Furion_Teaching_Manual/28-/346/250/241/347/265/204/345/214/226/351/226/213/347/231/274.md +68 -0
  37. package/knowledge/Furion_Teaching_Manual/29-/346/265/201/350/256/212/347/211/251/344/273/266Clay.md +313 -0
  38. package/knowledge/Furion_Teaching_Manual/30-/350/204/253/346/225/217/350/231/225/347/220/206/357/274/210Sensitive Detection).md" +168 -0
  39. package/knowledge/Furion_Teaching_Manual/32-/346/234/203/350/251/261/345/222/214/347/213/200/346/205/213/347/256/241/347/220/206.md +147 -0
  40. package/knowledge/Furion_Teaching_Manual/33-IPC/347/250/213/345/272/217/351/200/232/350/250/212.md +98 -0
  41. package/knowledge/Furion_Teaching_Manual/34-2-Docker/351/203/250/347/275/262.md +313 -0
  42. package/knowledge/Furion_Teaching_Manual/34-3-Nginx/351/203/250/347/275/262.md +157 -0
  43. package/knowledge/Furion_Teaching_Manual/34-8-WindowsService/351/203/250/347/275/262.md +112 -0
  44. package/knowledge/Furion_Teaching_Manual/35-1-Docker/347/222/260/345/242/203/346/214/201/347/272/214/351/203/250/347/275/262Jenkins.md +169 -0
  45. package/knowledge/Furion_Teaching_Manual/36-1-/345/226/256/345/205/203/346/270/254/350/251/246/346/225/264/345/220/210/346/270/254/350/251/246.md +275 -0
  46. package/knowledge/Furion_Teaching_Manual/36-3-/345/237/272/346/272/226/346/270/254/350/251/246Benchmarking.md +80 -0
  47. package/knowledge/attributes.md +153 -0
  48. package/knowledge/config.md +147 -0
  49. package/knowledge/entity.md +115 -0
  50. package/knowledge/event.md +124 -0
  51. package/knowledge/plugin.md +136 -0
  52. package/knowledge/service.md +146 -0
  53. package/knowledge/sqlsugar.md +172 -0
  54. package/knowledge/vue-typescript.md +338 -0
  55. package/package.json +3 -2
@@ -0,0 +1,336 @@
1
+ # 10.1 SqlSugar 整合
2
+
3
+ > **關於 SqlSugar**:如果選擇使用 SqlSugar 作為 ORM 資料庫操作,可直接安裝 `Furion.Pure` 和 `SqlSugarCore` 套件即可,之後根據 SqlSugar 官方文件教學使用。
4
+
5
+ ---
6
+
7
+ ## 10.1.1 SqlSugar ORM
8
+
9
+ SqlSugar 是 .NET/C# 平台非常優秀的 ORM 框架,目前 NuGet 總下載突破 1000K,Github 關注量也高達 3.7K,是目前當之無愧的國產優秀 ORM 框架之一。SqlSugar 高效能,具有百萬級插入、更新大數據分表等特色功能。
10
+
11
+ ---
12
+
13
+ ## 10.1.2 功能介紹
14
+
15
+ - 支援 SqlServer、MySql、PgSql、Oracle 百萬級插入和更新
16
+ - 支援全自動分表
17
+ - 支援多庫交易
18
+ - 支援 CodeFirst
19
+ - 支援聯表查詢、巢狀查詢、導航查詢、子查詢和動態 JSON 查詢等查詢操作
20
+ - 支援設定查詢
21
+ - 支援工具產生實體和程式碼產生實體
22
+ - 支援資料庫 MySql、SqlServer、Sqlite、Oracle、PostgreSQL、達夢、人大金倉、神通資料庫
23
+
24
+ ---
25
+
26
+ ## 10.1.3 官網文件
27
+
28
+ | 入門 | 查詢 | 插入 | 更新 | 刪除 |
29
+ |------|------|------|------|------|
30
+ | [安裝](https://www.donet5.com/Home/Doc) | [簡單查詢](https://www.donet5.com/Home/Doc) | 增 | 改 | 刪 |
31
+ | [入門](https://www.donet5.com/Home/Doc) | [聯表](https://www.donet5.com/Home/Doc) | | | |
32
+
33
+ ---
34
+
35
+ ## 10.1.4 Furion 整合
36
+
37
+ ### 建立一個擴充類別
38
+
39
+ ```csharp
40
+ public static class SqlsugarSetup
41
+ {
42
+ public static void AddSqlsugarSetup(this IServiceCollection services, IConfiguration configuration, string dbName = "db_master")
43
+ {
44
+ var configConnection = new ConnectionConfig()
45
+ {
46
+ DbType = SqlSugar.DbType.MySql,
47
+ ConnectionString = configuration.GetConnectionString(dbName),
48
+ IsAutoCloseConnection = true,
49
+ };
50
+
51
+ SqlSugarScope sqlSugar = new SqlSugarScope(configConnection,
52
+ db =>
53
+ {
54
+ // 單例參數設定,所有上下文生效
55
+ db.Aop.OnLogExecuting = (sql, pars) =>
56
+ {
57
+ // Console.WriteLine(sql); // 輸出 SQL
58
+ };
59
+ });
60
+
61
+ services.AddSingleton<ISqlSugarClient>(sqlSugar); // SqlSugarScope 用 AddSingleton
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### 使用注入
67
+
68
+ ```csharp
69
+ // 1. 建構函式注入
70
+ SqlSugar.ISqlSugarClient db;
71
+ public WeatherForecastController(ISqlSugarClient db)
72
+ {
73
+ this.db = db;
74
+ }
75
+
76
+ // 2. 手動取得
77
+ App.GetService<ISqlSugarClient>();
78
+ ```
79
+
80
+ ### 在 Startup.cs 中註冊
81
+
82
+ ```csharp
83
+ services.AddSqlsugarSetup(App.Configuration);
84
+ ```
85
+
86
+ > **小知識**:如果需要多庫設定,可查看 https://www.donet5.com/home/Doc?typeId=2246
87
+
88
+ ---
89
+
90
+ ## 10.1.5 特色功能
91
+
92
+ ### 10.1.5.1 聯表查詢
93
+
94
+ ```csharp
95
+ var query5 = db.Queryable<Order>()
96
+ .LeftJoin<Custom>((o, cus) => o.CustomId == cus.Id)
97
+ .LeftJoin<OrderItem>((o, cus, oritem) => o.Id == oritem.OrderId)
98
+ .Where(o => o.Id == 1)
99
+ .Select((o, cus) => new ViewOrder { Id = o.Id, CustomName = cus.Name })
100
+ .ToList();
101
+ ```
102
+
103
+ 產生的 SQL:
104
+
105
+ ```sql
106
+ SELECT [o].[Id] AS [Id], [cus].[Name] AS [CustomName]
107
+ FROM [Order] o
108
+ Left JOIN [Custom] cus ON ([o].[CustomId] = [cus].[Id])
109
+ Left JOIN [OrderDetail] oritem ON ([o].[Id] = [oritem].[OrderId])
110
+ WHERE ([o].[Id] = @Id0)
111
+ ```
112
+
113
+ ### 10.1.5.2 分頁查詢
114
+
115
+ ```csharp
116
+ int pageIndex = 1;
117
+ int pageSize = 20;
118
+ int totalCount = 0;
119
+ var page = db.Queryable<Student>().ToPageList(pageIndex, pageSize, ref totalCount);
120
+ ```
121
+
122
+ ### 10.1.5.3 動態運算式
123
+
124
+ ```csharp
125
+ var names = new string[] { "a", "b" };
126
+ Expressionable<Order> exp = new Expressionable<Order>();
127
+
128
+ foreach (var item in names)
129
+ {
130
+ exp.Or(it => it.Name.Contains(item.ToString()));
131
+ }
132
+
133
+ var list = db.Queryable<Order>().Where(exp.ToExpression()).ToList();
134
+ ```
135
+
136
+ 產生的 SQL:
137
+
138
+ ```sql
139
+ SELECT [Id],[Name],[Price],[CreateTime],[CustomId]
140
+ FROM [Order]
141
+ WHERE (
142
+ ([Name] like '%'+ CAST(@MethodConst0 AS NVARCHAR(MAX))+'%') OR
143
+ ([Name] like '%'+ CAST(@MethodConst1 AS NVARCHAR(MAX))+'%')
144
+ )
145
+ ```
146
+
147
+ ### 10.1.5.4 倉儲方法
148
+
149
+ 新建一個倉儲類別,如果想擴充方法寫到倉儲類別中:
150
+
151
+ ```csharp
152
+ public class Repository<T> : SimpleClient<T> where T : class, new()
153
+ {
154
+ public Repository(ISqlSugarClient context = null) : base(context)
155
+ {
156
+ base.Context = App.GetService<ISqlSugarClient>(); // 用手動取得方式支援切換倉儲
157
+ }
158
+ }
159
+ ```
160
+
161
+ 繼承倉儲類別就可以使用倉儲 API 了:
162
+
163
+ ```csharp
164
+ // 查詢
165
+ var data1 = base.GetById(1); // 根據 id 查詢
166
+ var data4 = base.GetSingle(it => it.Id == 1); // 查詢單條記錄
167
+ var data = base.GetFirst(it => it.Id == 1); // 查詢第一條記錄
168
+ var data2 = base.GetList(); // 查詢所有
169
+ var data3 = base.GetList(it => it.Id == 1); // 根據條件查詢
170
+
171
+ var p = new PageModel() { PageIndex = 1, PageSize = 2 };
172
+ var data5 = base.GetPageList(it => it.Name == "xx", p);
173
+ var data6 = base.GetPageList(it => it.Name == "xx", p, it => it.Name, OrderByType.Asc);
174
+
175
+ List<IConditionalModel> conModels = new List<IConditionalModel>();
176
+ conModels.Add(new ConditionalModel() { FieldName = "id", ConditionalType = ConditionalType.Equal, FieldValue = "1" });
177
+ var data7 = base.GetPageList(conModels, p, it => it.Name, OrderByType.Asc);
178
+
179
+ base.AsQueryable().Where(x => x.Id == 1).ToList();
180
+
181
+ // 插入
182
+ base.Insert(insertObj);
183
+ base.InsertRange(InsertObjs);
184
+ var id = base.InsertReturnIdentity(insertObj);
185
+ base.AsInsertable(insertObj).ExecuteCommand();
186
+
187
+ // 刪除
188
+ base.Delete(insertObj);
189
+ base.DeleteById(1);
190
+ base.DeleteByIds(new object[] { 1, 2 });
191
+ base.Delete(it => it.Id == 1);
192
+ base.AsDeleteable().Where(it => it.Id == 1).ExecuteCommand();
193
+
194
+ // 更新
195
+ base.Update(insertObj);
196
+ base.UpdateRange(InsertObjs);
197
+ base.Update(it => new Order() { Name = "a" }, it => it.Id == 1);
198
+ base.AsUpdateable(insertObj).UpdateColumns(it => new { it.Name }).ExecuteCommand();
199
+
200
+ // 進階操作
201
+ base.AsSugarClient // 取得完整的 db 物件
202
+ base.AsTenant // 取得多庫相關操作
203
+
204
+ // 切換倉儲
205
+ base.ChangeRepository<Repository<OrderItem>>() // 支援多租戶和擴充方法
206
+ base.Change<OrderItem>() // 只支援自帶方法和單庫
207
+ ```
208
+
209
+ ### 10.1.5.5 多庫交易
210
+
211
+ ```csharp
212
+ SqlSugarClient db = new SqlSugarClient(new List<ConnectionConfig>()
213
+ {
214
+ new ConnectionConfig() { ConfigId = "0", DbType = DbType.SqlServer, ConnectionString = Config.ConnectionString, IsAutoCloseConnection = true },
215
+ new ConnectionConfig() { ConfigId = "1", DbType = DbType.MySql, ConnectionString = Config.ConnectionString4, IsAutoCloseConnection = true }
216
+ });
217
+
218
+ var mysqldb = db.GetConnection("1"); // MySQL db
219
+ var sqlServerdb = db.GetConnection("0"); // SQL Server db
220
+
221
+ db.BeginTran();
222
+
223
+ mysqldb.Insertable(new Order()
224
+ {
225
+ CreateTime = DateTime.Now,
226
+ CustomId = 1,
227
+ Name = "a",
228
+ Price = 1
229
+ }).ExecuteCommand();
230
+
231
+ mysqldb.Queryable<Order>().ToList();
232
+ sqlServerdb.Queryable<Order>().ToList();
233
+
234
+ db.CommitTran();
235
+ ```
236
+
237
+ ### 10.1.5.6 單例模式
238
+
239
+ ```csharp
240
+ public static SqlSugarScope Db = new SqlSugarScope(new ConnectionConfig()
241
+ {
242
+ DbType = SqlSugar.DbType.SqlServer,
243
+ ConnectionString = Config.ConnectionString,
244
+ IsAutoCloseConnection = true
245
+ },
246
+ db =>
247
+ {
248
+ db.Aop.OnLogExecuting = (s, p) =>
249
+ {
250
+ Console.WriteLine(s);
251
+ };
252
+ });
253
+
254
+ using (var tran = Db.UseTran())
255
+ {
256
+ new Test2().Insert(XX);
257
+ new Test1().Insert(XX);
258
+
259
+ tran.CommitTran();
260
+ }
261
+ ```
262
+
263
+ ### 10.1.5.7 全域過濾器
264
+
265
+ ```csharp
266
+ db.QueryFilter.Add(new TableFilterItem<Order>(it => it.Name.Contains("a")));
267
+
268
+ db.Queryable<Order>().ToList();
269
+ // SELECT [Id],[Name],[Price],[CreateTime],[CustomId] FROM [Order]
270
+ // WHERE ([Name] like '%'+@MethodConst0+'%')
271
+
272
+ db.Queryable<OrderItem, Order>((i, o) => i.OrderId == o.Id)
273
+ .Where(i => i.OrderId != 0)
274
+ .Select("i.*").ToList();
275
+ // SELECT i.* FROM [OrderDetail] i, [Order] o
276
+ // WHERE ([i].[OrderId] = [o].[Id]) AND ([i].[OrderId] <> @OrderId0)
277
+ // AND ([o].[Name] like '%'+@MethodConst1+'%')
278
+ ```
279
+
280
+ ### 10.1.5.8 新增或更新
281
+
282
+ ```csharp
283
+ var x = Db.Storageable(list2).ToStorage();
284
+ x.AsInsertable.ExecuteCommand();
285
+ x.AsUpdateable.ExecuteCommand();
286
+
287
+ var x = Db.Storageable(list).SplitInsert(it => !it.Any()).ToStorage();
288
+ x.AsInsertable.ExecuteCommand();
289
+ ```
290
+
291
+ ### 10.1.5.9 自動分表
292
+
293
+ ```csharp
294
+ [SplitTable(SplitType.Year)]
295
+ [SugarTable("SplitTestTable_{year}{month}{day}")]
296
+ public class SplitTestTable
297
+ {
298
+ [SugarColumn(IsPrimaryKey = true)]
299
+ public long Id { get; set; }
300
+
301
+ public string Name { get; set; }
302
+
303
+ [SplitField]
304
+ public DateTime CreateTime { get; set; }
305
+ }
306
+
307
+ var list = db.Queryable<OrderSpliteTest>()
308
+ .SplitTable(DateTime.Now.Date.AddYears(-1), DateTime.Now)
309
+ .ToPageList(1, 2);
310
+ ```
311
+
312
+ ### 10.1.5.10 大數據插入、更新、插入或更新
313
+
314
+ ```csharp
315
+ // 插入百萬筆只需幾秒
316
+ db.Fastest<RealmAuctionDatum>().BulkCopy(GetList());
317
+
318
+ // 更新百萬筆只需幾秒
319
+ db.Fastest<RealmAuctionDatum>().BulkUpdate(GetList());
320
+ db.Fastest<RealmAuctionDatum>().BulkUpdate(GetList(), new string[] { "id" }, new string[] { "name", "time" }); // 無主鍵
321
+
322
+ // 存在則更新,不存在則插入
323
+ var x = db.Storageable<Order>(data).ToStorage();
324
+ x.BulkCopy();
325
+ x.BulkUpdate();
326
+
327
+ // 設定表名
328
+ db.Fastest<RealmAuctionDatum>().AS("tableName").BulkCopy(GetList());
329
+
330
+ // 設定分頁
331
+ db.Fastest<Order>().PageSize(300000).BulkCopy(insertObjs);
332
+ ```
333
+
334
+ ### 10.1.5.11 更多功能
335
+
336
+ 可查閱 [SqlSugar 官網](https://www.donet5.com/Home/Doc)。
@@ -0,0 +1,271 @@
1
+ # Furion SaaS 多租戶筆記
2
+
3
+ ---
4
+
5
+ ## 1. 概念說明
6
+
7
+ ### SaaS
8
+ 透過 Internet 提供軟體服務的模式,廠商統一部署應用,客戶按需租用,無需自行維護基礎設施。
9
+
10
+ ### 多租戶
11
+ 單一應用實例服務多個租戶(客戶),**重點在於同一套程式下實現多用戶資料隔離**。
12
+
13
+ ---
14
+
15
+ ## 2. 三種隔離方案比較
16
+
17
+ | 方案 | 說明 | 隔離級別 | 成本 | 適用場景 |
18
+ |------|------|----------|------|----------|
19
+ | **Database** | 一租戶一資料庫 | 最高 | 高 | 銀行、醫院等高安全需求 |
20
+ | **Schema** | 共享 DB,每租戶獨立 Schema | 中 | 中 | 中小型企業 |
21
+ | **TenantId** | 共享 DB + Schema,以欄位區分 | 最低 | 最低 | 中小型企業(最常用) |
22
+
23
+ > ⚠️ 一旦 DbContext 繼承租戶任意介面,即自動啟用多租戶功能。
24
+
25
+ ---
26
+
27
+ ## 3. 方案一:基於 TenantId(最常用)
28
+
29
+ ### 步驟概覽
30
+ 1. 建立租戶 DbContext
31
+ 2. 注冊租戶 DbContext
32
+ 3. 新增 Tenant 種子資料(Code First 才需要)
33
+ 4. 執行 Migration 建立 Tenant 表
34
+ 5. 實作 `IMultiTenantOnTable`
35
+ 6. 實作 `IModelBuilderFilter`(查詢過濾)
36
+ 7. 覆寫 `SavingChangesEvent`(新增/更新處理)
37
+
38
+ ### 3.1 建立租戶 DbContext
39
+
40
+ ```csharp
41
+ [AppDbContext("Sqlite3ConnectionString", DbProvider.Sqlite)]
42
+ public class MultiTenantDbContext : AppDbContext<MultiTenantDbContext, MultiTenantDbContextLocator>
43
+ {
44
+ public MultiTenantDbContext(DbContextOptions<MultiTenantDbContext> options) : base(options) { }
45
+ }
46
+ ```
47
+
48
+ > ⚠️ 多租戶 DbContext 必須指定 `MultiTenantDbContextLocator`。
49
+
50
+ ### 3.2 注冊服務
51
+
52
+ ```csharp
53
+ services.AddDatabaseAccessor(options =>
54
+ {
55
+ options.AddDbPool<FurionDbContext>();
56
+ options.AddDbPool<MultiTenantDbContext, MultiTenantDbContextLocator>();
57
+ });
58
+ ```
59
+
60
+ ### 3.3 種子資料(Code First)
61
+
62
+ ```csharp
63
+ public class TenantSeedData : IEntitySeedData<Tenant, MultiTenantDbContextLocator>
64
+ {
65
+ public IEnumerable<Tenant> HasData(DbContext dbContext, Type dbContextLocator)
66
+ {
67
+ return new List<Tenant>
68
+ {
69
+ new Tenant
70
+ {
71
+ TenantId = Guid.Parse("383AFB88-F519-FFF8-B364-6D563BF3687F"),
72
+ Name = "預設租戶",
73
+ Host = "localhost:44313",
74
+ CreatedTime = DateTime.Parse("2020-10-06 20:19:07")
75
+ }
76
+ };
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### 3.4 Migration
82
+
83
+ ```bash
84
+ Add-Migration add_tenant_table -Context MultiTenantDbContext
85
+ Update-Database -Context MultiTenantDbContext
86
+ ```
87
+
88
+ ### 3.5 實作 `IMultiTenantOnTable` + `IModelBuilderFilter`
89
+
90
+ ```csharp
91
+ [AppDbContext("Sqlite3ConnectionString", DbProvider.Sqlite)]
92
+ public class FurionDbContext : AppDbContext<FurionDbContext>, IMultiTenantOnTable, IModelBuilderFilter
93
+ {
94
+ public FurionDbContext(DbContextOptions<FurionDbContext> options) : base(options) { }
95
+
96
+ // 取得目前租戶 TenantId
97
+ public object GetTenantId()
98
+ {
99
+ return base.Tenant?.TenantId ?? Guid.Empty;
100
+ // 自訂邏輯:直接 return 你的 TenantId;
101
+ }
102
+
103
+ // 設定全域查詢過濾
104
+ public void OnCreating(ModelBuilder modelBuilder, EntityTypeBuilder entityBuilder,
105
+ DbContext dbContext, Type dbContextLocator)
106
+ {
107
+ entityBuilder.HasQueryFilter(BuildTenantQueryFilter(entityBuilder, dbContext));
108
+ }
109
+ }
110
+ ```
111
+
112
+ ### 3.6 覆寫 `SavingChangesEvent`(新增自動帶入、更新排除 TenantId)
113
+
114
+ ```csharp
115
+ protected override void SavingChangesEvent(DbContextEventData eventData, InterceptionResult<int> result)
116
+ {
117
+ var dbContext = eventData.Context;
118
+ var entities = dbContext.ChangeTracker.Entries()
119
+ .Where(u => u.State == EntityState.Added
120
+ || u.State == EntityState.Modified
121
+ || u.State == EntityState.Deleted);
122
+
123
+ foreach (var entity in entities)
124
+ {
125
+ switch (entity.State)
126
+ {
127
+ case EntityState.Added:
128
+ // 新增時自動設定 TenantId
129
+ entity.Property(nameof(Entity.TenantId)).CurrentValue = GetTenantId();
130
+ break;
131
+ case EntityState.Modified:
132
+ // 更新時排除 TenantId 被修改
133
+ entity.Property(nameof(Entity.TenantId)).IsModified = false;
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ## 4. 方案二:基於 Database(獨立資料庫)
143
+
144
+ ### 步驟概覽
145
+ 1~4 同 TenantId 方案,但種子資料需多設 `ConnectionString`
146
+ 5. 實作 `IMultiTenantOnDatabase`
147
+ 6. 覆寫 `OnConfiguring` 套用連線字串
148
+
149
+ ### 4.1 種子資料差異(需含 ConnectionString)
150
+
151
+ ```csharp
152
+ new Tenant
153
+ {
154
+ TenantId = Guid.Parse("..."),
155
+ Name = "預設租戶",
156
+ Host = "localhost:44313",
157
+ ConnectionString = "Data Source=./Furion.db" // 重點
158
+ }
159
+ ```
160
+
161
+ ### 4.2 實作 `IMultiTenantOnDatabase` + 覆寫 `OnConfiguring`
162
+
163
+ ```csharp
164
+ public class FurionDbContext : AppDbContext<FurionDbContext>, IMultiTenantOnDatabase
165
+ {
166
+ public FurionDbContext(DbContextOptions<FurionDbContext> options) : base(options) { }
167
+
168
+ public string GetDatabaseConnectionString()
169
+ {
170
+ return base.Tenant?.ConnectionString ?? "預設連線字串";
171
+ // 自訂邏輯:直接 return 你的連線字串;
172
+ }
173
+
174
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
175
+ {
176
+ optionsBuilder.UseSqlite(GetDatabaseConnectionString());
177
+ base.OnConfiguring(optionsBuilder);
178
+ }
179
+ }
180
+ ```
181
+
182
+ > ⚠️ Code First 時需手動指定 Migration 程式集名稱:
183
+ > ```csharp
184
+ > optionsBuilder.UseSqlite(GetDatabaseConnectionString(), options =>
185
+ > {
186
+ > options.MigrationsAssembly("My.Migrations");
187
+ > });
188
+ > ```
189
+
190
+ ---
191
+
192
+ ## 5. 方案三:基於 Schema
193
+
194
+ ### 步驟概覽
195
+ 1~4 同前,種子資料需多設 `Schema`
196
+ 5. 實作 `IMultiTenantOnSchema`
197
+
198
+ ### 5.1 種子資料差異(需含 Schema)
199
+
200
+ ```csharp
201
+ new Tenant { ..., Schema = "dbo" }
202
+ new Tenant { ..., Schema = "furion" }
203
+ ```
204
+
205
+ ### 5.2 實作 `IMultiTenantOnSchema`
206
+
207
+ ```csharp
208
+ [AppDbContext("Sqlite3ConnectionString", DbProvider.Sqlite)]
209
+ public class FurionDbContext : AppDbContext<FurionDbContext>, IMultiTenantOnSchema
210
+ {
211
+ public FurionDbContext(DbContextOptions<FurionDbContext> options) : base(options) { }
212
+
213
+ public string GetSchemaName()
214
+ {
215
+ return base.Tenant?.Schema ?? "dbo";
216
+ // 自訂邏輯:直接 return 你的 Schema;
217
+ }
218
+ }
219
+ ```
220
+
221
+ > 💡 Code First Migration 時需分批執行,每次切換預設 Schema 後再跑一次 Migration。
222
+
223
+ ---
224
+
225
+ ## 6. 自訂 Tenant 類型
226
+
227
+ ```csharp
228
+ // 啟用自訂
229
+ services.AddDatabaseAccessor(options =>
230
+ {
231
+ options.CustomizeMultiTenants(); // 可傳入自訂 TenantId 欄位名
232
+ options.AddDbPool<FurionDbContext>();
233
+ });
234
+
235
+ // 自訂租戶類
236
+ public class MyTenant : IEntity<MultiTenantDbContextLocator>
237
+ {
238
+ [Key]
239
+ public Guid TenantId { get; set; }
240
+ public string Name { get; set; }
241
+ public string Host { get; set; }
242
+ }
243
+
244
+ // 查詢自訂租戶
245
+ var tenantDbContext = Db.GetDbContext<MultiTenantDbContextLocator>();
246
+ var myTenant = tenantDbContext.Set<MyTenant>();
247
+ ```
248
+
249
+ ---
250
+
251
+ ## 7. 刷新租戶快取
252
+
253
+ Furion 首次查詢時會將租戶資料快取,**更新租戶資料後需手動刷新**:
254
+
255
+ ```csharp
256
+ using Furion.DatabaseAccessor.Extensions;
257
+
258
+ // 在更新租戶資訊後呼叫刷新方法
259
+ ```
260
+
261
+ ---
262
+
263
+ ## 快速選型
264
+
265
+ ```
266
+ 需要最高資料隔離?
267
+ ├─ 是 → Database 方案(獨立 DB)
268
+ └─ 否 → 成本優先?
269
+ ├─ 是 → TenantId 方案(最省、最常用)
270
+ └─ 否 → Schema 方案(邏輯隔離)
271
+ ```