学习一项知识,最好的方式还是实践。写一个简单的增删改示例,实现对货品的简单管理,对ABP框架如何工作也能有一个更好的认知。
注意:示例中使用的ABP框架版本为5.0,运行环境为.Net Core,数据库为Mysql
1.下载代码模板
从官网下载一个代码模板(ASP.NET Core v3.x版本,不包含登录、用户、角色管理界面),可直接编译运行。解压后文件结构如下:
- xx.Application:应用层
- xx.Core:领域层
- xx.EntityFrameworkCore:基础设施层,可手动将ORM切换为NHibernate或Dapper
- xx.Web:分布式服务层和展现层
2.创建实体类
在领域层项目AbpDemo.Core中新建一个货品的实体类,包含货品名称、货品类型、存放位置、货品数量等。
/// <summary>
/// 货品实体类
/// </summary>
public class Goods:FullAuditedEntity<string>
{
/// <summary>
/// 货品名称
/// </summary>
public string GoodsName { get; set; }
/// <summary>
/// 货品类型
/// </summary>
public string GoodsType { get; set; }
/// <summary>
/// 存放位置
/// </summary>
public string Location { get; set; }
/// <summary>
/// 货品数量
/// </summary>
public int GoodsNum { get; set; }
}
货品实体类继承了ABP框架提供的泛型基类FullAuditedEntity。泛型用于规定主键的数据类型,这里主键用的string类型。基类提供了创建审计、修改审计、删除审计、主键等基本功能,框架自带的功能能为日常开发节省不少工作量。当然除了FullAuditedEntity基类,还有AuditedEntity、Entity等多种基类,也可以使用自定义的基类。
3.创建数据库映射关系及仓储
本次示例中数据库使用的是Mysql,而代码模板中基础设施层AbpDemp.EntityFrameworkCore默认使用的SqlServer,所以要先修改连接数据的相关代码。
首先,使用Nuget管理器安装连接Mysql所需的依赖包Pomelo.EntityFrameworkCore.MySql。
其次,修改数据库连接上下文配置项
public static class DbContextOptionsConfigurer
{
public static void Configure(
DbContextOptionsBuilder<AbpDemoDbContext> dbContextOptions,
string connectionString
)
{
/* 配置数据库连接上下文 */
//SQL Server
//dbContextOptions.UseSqlServer(connectionString);
//MySql
dbContextOptions.UseMySql(connectionString);
}
}
然后,将实体类的数据集合添加至数据上下文中,并编写实体类与数据库表的映射关系。
public class AbpDemoDbContext : AbpDbContext
{
//在此处为实体类添加DbSet属性
public DbSet<Goods> Goods { get; set; }
public AbpDemoDbContext(DbContextOptions<AbpDemoDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//实体对象与数据库对象映射
modelBuilder.ApplyConfiguration(new GoodsMap());
}
}
其中,实体类与数据库表的映射关系为:
public class GoodsMap : IEntityTypeConfiguration<Goods>
{
public void Configure(EntityTypeBuilder<Goods> builder)
{
builder.ToTable("INFO_Goods");//表名
builder.HasKey(m => m.Id);//主键
builder.Property(m => m.Id).HasColumnName("RowGuid");//列名
builder.Property(m => m.GoodsName)
//.HasColumnType("varchar")//数据类型
.HasMaxLength(100)//数据长度
.HasDefaultValue("默认值")//默认值
.IsRequired(true);//true不可为空/false可为空
builder.Property(m => m.Location).HasMaxLength(100);
builder.Property(m => m.GoodsNum).HasMaxLength(10).HasDefaultValue(0);
}
}
当然也可以不去设置实体类与数据库表的映射关系,那样的话数据库表的相关信息就跟实体类保持一致了。
最后,使用程序包管理控制台设置默认项目为AbpDemp.EntityFrameworkCore,并执行如下命令以生成数据库。
add-migration Add_Goods
update-database
4.创建数据传输对象
在应用层AbpDemo.Application添加CreateGoodsDto(新增)、UpdateGoodsDto(修改)、DetailGoodsDto(详情)三个数据传输对象。可以将数据传输对象理解为对实体进行操作是的输入输出,不同的操作对应不同的输入或输出,数据传输对象与实体之间也存在着必要的关联。
/// <summary>
/// 新增货品-数据传输对象
/// </summary>
[AutoMapTo(typeof(Goods))]
public class CreateGoodsDto
{
/// <summary>
/// 货品名称
/// </summary>
public string GoodsName { get; set; }
/// <summary>
/// 货品类型
/// </summary>
public string GoodsType { get; set; }
/// <summary>
/// 存放位置
/// </summary>
public string Location { get; set; }
}
/// <summary>
/// 修改货品-数据传输对象
/// </summary>
[AutoMapTo(typeof(Goods))]
public class UpdateGoodsDto:EntityDto<string>
{
/// <summary>
/// 货品类型
/// </summary>
public string GoodsType { get; set; }
/// <summary>
/// 存放位置
/// </summary>
public string Location { get; set; }
}
/// <summary>
/// 货品详情-数据传输对象
/// </summary>
[AutoMapFrom(typeof(Goods))]
public class DetailGoodsDto:EntityDto<string>
{
/// <summary>
/// 货品名称
/// </summary>
public string GoodsName { get; set; }
/// <summary>
/// 货品类型
/// </summary>
public string GoodsType { get; set; }
/// <summary>
/// 存放位置
/// </summary>
public string Location { get; set; }
/// <summary>
/// 货品数量
/// </summary>
public int GoodsNum { get; set; }
}
这里使用了ABP框架自带组件Abp.AutoMapper对数据传输对象和实体类之间的字段进行映射,这样在类的实例化时就能节省不少的代码编写量。另外根据数据传输对象所要展现的字段属性不同也继承了不同基类。
5.定义应用服务接口并实现
如果说上一步的创建数据传输对象是对数据操作的输入输出进行定义,那么应用服务接口和应用服务类则是对数据操作的函数方法进行定义并实现。
首先在应用层AbpDemo.Application创建应用服务接口IGoodsAppService。定义了新增、修改、详情、删除四个接口方法,同时继承了接口IApplicationService,这是ABP框架中应用层方法的约定。
/// <summary>
/// 货品管理-应用服务接口
/// </summary>
public interface IGoodsAppService: IApplicationService
{
/// <summary>
/// 新增
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<DetailGoodsDto> Create(CreateGoodsDto input);
/// <summary>
/// 修改
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<DetailGoodsDto> Update(UpdateGoodsDto input);
/// <summary>
/// 详情
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<DetailGoodsDto> Detail(string id);
/// <summary>
/// 删除
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
Task<int> Delete(IEnumerable<string> ids);
}
然后在应用层AbpDemo.Application创建应用服务GoodsAppService并继承接口IGoodsAppService。在构造函数中通过依赖注入引入仓储,并实现新增、修改、详情、删除四个方法。
/// <summary>
/// 货品管理-应用服务
/// </summary>
public class GoodsAppService: IGoodsAppService
{
/// <summary>
/// 仓储
/// </summary>
private readonly IRepository<Goods, string> _repository;
public GoodsAppService(IRepository<Goods,string> repository)
{
_repository = repository;
}
#region 方法
#region 新增
public Task<DetailGoodsDto> Create(CreateGoodsDto input)
{
Goods entity = input.MapTo<Goods>();
entity.Id = Guid.NewGuid().ToString();
Goods result = _repository.Insert(entity);
DetailGoodsDto output = result.MapTo<DetailGoodsDto>();
return Task.FromResult(output);
}
#endregion
#region 删除
public Task<int> Delete(IEnumerable<string> ids)
{
int num = 0;
foreach (string id in ids)
{
_repository.Delete(id);
num++;
}
return Task.FromResult(num);
}
#endregion
#region 详情
public Task<DetailGoodsDto> Detail(string id)
{
Goods result = _repository.Get(id);
DetailGoodsDto output = result.MapTo<DetailGoodsDto>();
return Task.FromResult(output);
}
#endregion
#region 修改
public Task<DetailGoodsDto> Update(UpdateGoodsDto input)
{
Goods entity = input.MapTo<Goods>();
Goods result = _repository.Update(entity);
DetailGoodsDto output = result.MapTo<DetailGoodsDto>();
return Task.FromResult(output);
}
#endregion
#endregion
}
经过上述步骤,一个简单的货品管理功能就完成了。过程中使用了很多ABP框架默认的配置和功能,当然每个步骤的具体代码细节都可以进行调整,比如数据库的映射、数据传输对象的映射等,在实际开发过程可以根据需要进行自定义修改。
不过如果实际开发中每个模块都按照这个步骤码下来确实有点耗时,好消息是市面上可以找到不少的自动化代码工具来完成上述工作,自己有能力的话也可以考虑自己开发一个工具。后面也考虑在上述步骤的基础上进行优化,逐步完善功能。
备注
理论上来讲,完成领域层和应用层的代码编写后,功能开发并不算完成。但由于ABP框架自带的动态生成API机制,分布式服务层AbpDemo.Web的代码可以省略。动态生成API的机制也并不是ABP框架的原创,这是ASP.NET Core本身就提供的。在模块启动类AbpDemoWebModule中对动态生成API接口进行了定义。
public override void PreInitialize()
{
//配置默认数据库连接字符串
Configuration.DefaultNameOrConnectionString = _appConfiguration.GetConnectionString(RunGoConsts.DefaultConnectionStringName);
//配置导航菜单
Configuration.Navigation.Providers.Add<RunGoNavigationProvider>();
//配置动态创建应用服务接口
/*若useConventionalHttpVerbs被设置为true(默认值),
*那么应用服务的方法类型会根据所使用HTTP谓词自动判定:
*Get:方法名以Get开头
*Put:方法名以Put或Update开头
*Delete:方法名以Delete或Remove开头
*Post:方法名以Post、Create或Insert开头
*Path:方法名以Path开头
*否则Post被作为Http谓词的默认设置
*/
Configuration.Modules.AbpAspNetCore()
.CreateControllersForAppServices(
typeof(RunGoApplicationModule).GetAssembly(),
"template",
useConventionalHttpVerbs: true
);
}
也可以注释Configuration.Modules.AbpAspNetCore().CreateControllersForAppServices这段代码,取消自动生成API接口,进而像普通的WebAPI项目一样创建控制器和API接口方法。