1. 程式人生 > 實用技巧 >redis 專題 (四)List操作

redis 專題 (四)List操作

型別掃描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 找到所有 AttributeEntityAttribute 的型別,就能解決了 :

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 上下文

AppAppSetup.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

AppContainerApp 物件的構成要素,

App 的本質只有兩樣

  • 字典型別的上下文
  • 所有 AppContainer

每一種 AppContainer 都會管理某一類的型別,
而這些型別都是通過 ScannableAttribute 掃描得到的。

比如,在 Reface.AppStarter 中,有負責 IOC 和負責 AutoConfig 的兩個 AppContainer
它們分別對標有 ComponentConfig 的型別進行不同的管理和處理。

根據這個分門別類管理的思想,所有的實體型別也應當由專門的 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