类型扫描 是 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