Reface.AppStarter 類型掃描 —— 獲得系統中所有的實體類型
- 2020 年 7 月 13 日
- 筆記
- c#, Framework, Reface, Reface.AppStarter
類型掃描 是 Reface.AppStarter 提供的最基本、最核心的功能。
AutoConfig , ComponentScan 等功能都是基於該功能完成的。
每一個使用 Reface.AppStarter 的人都可以訂製自己的掃描類型掃描邏輯。
例如
收集系統中所有的 實體 類型,並在系統啟動後執行 Code-First 的相關操作。
我們現在就以該示例為需求,開發一個能夠 掃描實體,並藉助第三方框架實現 CodeFirst 的示常式序。
1. 創建程式
創建一個名為 Reface.AppStarter.Demos.ScanEntities 的控制台項目用於承載我們的示常式序。
2. 添加 Reface.AppStarter 的 nuget 依賴
點擊訪問 Reface.AppStarter @ nuget 可以複製最新版本的 Reface.AppStarter 的命令行到 Package Manager 中。
3. 創建專屬的 Attribute
在 Reface.AppStarter 對類型的掃描是通過 Attribute 識別的。
Reface.AppStarter.Attributes.ScannableAttribute 表示該特徵允許被 AppSetup 掃描並記錄。
因此只要將我們的 Attribute 繼承於 ScannableAttribute 就可以被 AppSetup 掃描並記錄。
我們先創建一個目錄 Attributes,並在該目錄中創建特徵類型 EntityAttribute 讓其繼承於 ScannableAttribute
using Reface.AppStarter.Attributes;
namespace Reface.AppStarter.Demos.ScanEntities.Attributes
{
public class EntityAttribute : ScannableAttribute
{
}
}
所有標記了 EntityAttribute 的類型都會被 AppSetup 發現、記錄。
我們使用這些記錄就可以收集到系統內的所有實體,並進一步根據這些實體類型進行 Code-First 操作。
4. 創建專屬 AppModule
Reface.AppStarter 中對模組的依賴和增加都是通過 AppModule 完成的。
我們希望使用者添加對我們的 AppModule 依賴就可以掃描指定模組中的所有實體類型。
在應用時,我們希望形如下面的程式碼:
[ComponentScanAppModule] // IOC 組件掃描與註冊模組
[AutoConfigAppModule] // 自動配置模組
[EntityScanAppModule] // 實體操作模組
public class UserAppModule : AppModule
{
}
很明顯,我們需要創建一個名為 EntityScanAppModule 的類型。
我們創建一個目錄名為 AppModules,並將 EntityScanAppModule 創建在該目錄下。
此時,我們的目錄如下
- Reface.AppStarter.Demos.ScanEntities
- AppModules
EntityScanAppModule.cs
- Attributes
EntityAttribute.cs
Program.cs
此時的 EntityScanAppModule 是一個空白的 AppModule,
還不具有找到所有標記了 EntityAttribute 的類型的能力。
我們可以通過重寫 OnUsing 方法賦予此功能。
OnUsing 方法具備一個類型為 AppModuleUsingArguments 的參數。
- AppSetup , 此時啟動過程的 AppSetup 實例
- TargetAppModule , 以之前的 UserAppModule 為例,TargetAppModule 就是 UserAppModule 的實例
- UsingAppModule , 以之前的 UserAppModule 為例,UsingAppModule 就是 EntityScanAppModule 的實例,也就是 this 指向的實例
- ScannedAttributeAndTypeInfos , 從 TargetAppModule 中掃描所到的全部類型資訊
看到最後一個屬性,應該一切就簡單了。
從 ScannedAttributeAndTypeInfos 找到所有 Attribute 是 EntityAttribute 的類型,就能解決了 :
using Reface.AppStarter.AppModules;
using Reface.AppStarter.Demos.ScanEntities.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Reface.AppStarter.Demos.ScanEntities.AppModules
{
public class EntityScanAppModule : AppModule
{
public override void OnUsing(AppModuleUsingArguments args)
{
IEnumerable<Type> entityTypes = args
.ScannedAttributeAndTypeInfos
.Where(x => x.Attribute is EntityAttribute)
.Select(x => x.Type);
}
}
}
我們現在需要考慮的是,如何將 entityTypes 保存下來,以便在系統啟動後使用它們建表。
除了使用 靜態類 等常見手段保存這些資訊,Reface.AppStarter 也提供了兩種方式。
4.1 App 上下文
App 是 AppSetup.Start() 得到的實例。
App.Context 屬性是一個 Dictionary<String, Object> 對象,允許開發者自定義一些資訊掛載在上下文中,以便在其它位置使用它們。
AppSetup 在構建期間也可以預置一些資訊到 App.Context 中。
public override void OnUsing(AppModuleUsingArguments args)
{
IEnumerable<Type> entityTypes = args
.ScannedAttributeAndTypeInfos
.Where(x => x.Attribute is EntityAttribute)
.Select(x => x.Type);
args.AppSetup
.AppContext
.GetOrCreate<List<Type>>("EntityTypes", key =>
{
return new List<Type>();
})
.AddRange(entityTypes);
}
通過這種方式,當系統啟動後,我們可以通過 App.Context 得到所有掃描到的實體類型:
var app = AppSetup.Start<XXXAppModule>();
List<Type> entityTypes = app.Context
.GetOrCreate<List<Type>>("EntityTypes", key => new List<Type>());
// code-first here
4.2 AppContainerBuilder / AppContainer
AppContainer 是 App 對象的構成要素,
App 的本質只有兩樣
- 字典類型的上下文
- 所有 AppContainer
每一種 AppContainer 都會管理某一類的類型,
而這些類型都是通過 ScannableAttribute 掃描得到的。
比如,在 Reface.AppStarter 中,有負責 IOC 和負責 AutoConfig 的兩個 AppContainer。
它們分別對標有 Component 和 Config 的類型進行不同的管理和處理。
根據這個分門別類管理的思想,所有的實體類型也應當由專門的 AppContainer 管理所有的 實體類型。
5. 創建 EntityAppContainer
創建目錄 AppContainers ,
並在目錄內創建 EntityAppContainer ,
EntityAppContainer 需要繼承 BaseAppContainer ,
添加構造函數,傳入所有的 實體類型 ,
重寫 OnAppStarted 方法 , 實現 Code-First 功能。
using Reface.AppStarter.AppContainers;
using System;
using System.Collections.Generic;
namespace Reface.AppStarter.Demos.ScanEntities.AppContainers
{
public class EntityAppContainer : BaseAppContainer
{
// all entity type here
private readonly IEnumerable<Type> entityType;
public EntityAppContainer(IEnumerable<Type> entityType)
{
this.entityType = entityType;
}
public override void OnAppStarted(App app)
{
entityType.ForEach(x => Console.WriteLine("Do CODE-FIRST from type {0}", x));
}
}
}
很明顯,沒有 entityType ,我們無法直接構造出 EntityAppContainer 。
Reface.AppStarter 要求所有的 AppContainer 都是通過 AppContainerBuilder 創建得到的。
與 AppContainer 不同,AppContainerBuilder 是被託管在 AppSetup 實例中的,
開發者可以通過 AppSetup 的實例 訪問 指定類型的 AppContainerBuilder 。
AppContainerBuilder 一旦被 訪問 ,就會立刻創建,並在最終生成 App 實例時,構建成相應的 AppContainer 並移交給 App。
6. 創建 EntityAppContainerBuilder
創建目錄 AppContainerBuilders,
在目錄內創建類型 EntityAppContainerBuilder 繼承 BaseAppContainerBuilder,
並重寫 Build 方法。
using Reface.AppStarter.AppContainerBuilders;
using Reface.AppStarter.AppContainers;
using System;
namespace Reface.AppStarter.Demos.ScanEntities.AppContainerBuilders
{
public class EntityAppContainerBuilder : BaseAppContainerBuilder
{
public override IAppContainer Build(AppSetup setup)
{
throw new NotImplementedException();
}
}
}
很明顯,我們知道需要在 Build 中寫下這樣的程式碼
return new EntityAppContainer(entityTypes);
entityTypes 從何而來?
這個就簡單了,為 EntityAppContainerBuilder 添加一個 AddEntityType(Type type) 就行了。
using Reface.AppStarter.AppContainerBuilders;
using Reface.AppStarter.AppContainers;
using Reface.AppStarter.Demos.ScanEntities.AppContainers;
using System;
using System.Collections.Generic;
namespace Reface.AppStarter.Demos.ScanEntities.AppContainerBuilders
{
public class EntityAppContainerBuilder : BaseAppContainerBuilder
{
private readonly ICollection<Type> entityTypes;
public EntityAppContainerBuilder()
{
entityTypes = new List<Type>();
}
public void AddEntityType(Type type)
{
this.entityTypes.Add(type);
}
public override IAppContainer Build(AppSetup setup)
{
return new EntityAppContainer(entityTypes);
}
}
}
從這個類型不難發現,我們需要創建一個 EntityAppContainerBuilder ,並使用 AddEntityType 將實體類型加入。
最後的 Build 方法會由 AppSetup 內部執行。
7. 使用 EntityScanAppModule 操作 EntityAppContainerBuilder
現在回到之前的 EntityScanAppModule ,
從前向 App.Context 內預置資訊的程式碼可以刪除掉了。
我們先從 AppSetup 中獲取 EntityAppContainerBuilder 的實例,
配合上 AddEntityType,然後就一氣呵成了。
using Reface.AppStarter.AppModules;
using Reface.AppStarter.Demos.ScanEntities.AppContainerBuilders;
using Reface.AppStarter.Demos.ScanEntities.Attributes;
using System.Linq;
namespace Reface.AppStarter.Demos.ScanEntities.AppModules
{
public class EntityScanAppModule : AppModule
{
public override void OnUsing(AppModuleUsingArguments args)
{
EntityAppContainerBuilder builder = args.AppSetup.GetAppContainerBuilder<EntityAppContainerBuilder>();
args
.ScannedAttributeAndTypeInfos
.Where(x => x.Attribute is EntityAttribute)
.Select(x => x.Type)
.ForEach(x => builder.AddEntityType(x));
}
}
}
8. 準備我們的啟動程式
創建 DemoAppModule,
添加一些 Entity,
添加 EntityScanAppModule 的依賴,
啟動,即可測試我們的程式碼了。
using Reface.AppStarter.AppModules;
using Reface.AppStarter.Demos.ScanEntities.AppModules;
namespace Reface.AppStarter.Demos.ScanEntities
{
[EntityScanAppModule]
public class DemoAppModule : AppModule
{
}
}
在 Program.cs 中編寫啟動程式
using System;
namespace Reface.AppStarter.Demos.ScanEntities
{
class Program
{
static void Main(string[] args)
{
AppSetup.Start<DemoAppModule>();
Console.ReadLine();
}
}
}
控制台中就可以得到所有的實體類型
Do CODE-FIRST from type Reface.AppStarter.Demos.ScanEntities.Entities.Role
Do CODE-FIRST from type Reface.AppStarter.Demos.ScanEntities.Entities.User
附
文中項目程式碼可以從這裡下載 : Reface.AppStarter.Demos.ScanEntities @ Github