【ABP】项目示例(3)——仓储
仓储
在上一章节中,已经完成了领域层的聚合根和实体设计,在这一章节中,实现仓储层的部分功能
仓储作为领域模型和数据模型的桥梁,领域层不关注仓储是怎么实现持久化数据的。对于领域层,仓储层隐藏了持久化数据的细节,所以只需要将仓储接口定义在领域层,而具体的仓储实现则在仓储层,具体的ORM实现可以是Entity Framework,也可以是SqlSugar
本项目使用Entity Framework + MySql作为仓储层的具体实现
创建名称为General.Backend.EntityFrameworkCore的标准类库
在程序包管理控制台选中General.Backend.EntityFrameworkCore,执行以下命令安装ABP仓储域相关的Nuget包
Install-Package Volo.Abp.EntityFrameworkCore -v 8.3.0
Install-Package Volo.Abp.EntityFrameworkCore.MySQL -v 8.3.0
General.Backend.EntityFrameworkCore添加项目引用General.Backend.Domain
新建名称为GeneralDbContext的数据库上下文类,在其中配置各个聚合根和实体的DbSet以及指定从当前程序集加载领域模型与数据模型的映射关系
[ConnectionStringName("Default")]
public class GeneralDbContext : AbpDbContext<GeneralDbContext>
{
public DbSet<User> Users { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<RoleMenu> RoleMenus { get; set; }
public GeneralDbContext(DbContextOptions<GeneralDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
}
新建名称为GeneralEntityFrameworkCoreModule的仓储模块类,其中AddDefaultRepositories方法中的includeAllEntities参数设置为true,为每个实体生成默认的仓储实现,这样子所有的聚合根和实体都可以使用由ABP预先实现的基础通用数据访问方法
[DependsOn(
typeof(AbpEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreMySQLModule)
)]
public class GeneralEntityFrameworkCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<GeneralDbContext>(options =>
{
options.AddDefaultRepositories(includeAllEntities: true);
});
Configure<AbpDbContextOptions>(options =>
{
options.UseMySQL();
});
}
}
实体映射
在General.Backend.EntityFrameworkCore类库中新建名称为Mappings的文件夹,用于存放实体映射配置类
- 用户实体映射配置
/// <summary>
/// 用户实体映射配置
/// </summary>
public class UserMap : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable(UserConsts.UserTableName, table =>
{
table.HasComment(UserConsts.UserTableComment);
});
builder.ConfigureByConvention();
builder.Property(user => user.Id)
.HasComment("主键");
builder.Property(user => user.Account)
.HasMaxLength(UserConsts.MaxAccountLength)
.IsRequired()
.HasComment("账号");
builder.Property(user => user.Password)
.HasMaxLength(UserConsts.MaxPasswordLength)
.IsRequired()
.HasComment("密码");
builder.Property(user => user.Name)
.HasMaxLength(UserConsts.MaxNameLength)
.IsRequired()
.HasComment("姓名");
builder.Property(user => user.Contact)
.HasMaxLength(UserConsts.MaxContactLength)
.HasComment("联系信息");
builder.Property(user => user.Address)
.HasMaxLength(UserConsts.MaxAddressLength)
.HasComment("地址");
builder.Property(user => user.IsFrozen)
.IsRequired()
.HasComment("是否冻结,1:未冻结,2:已冻结");
builder.Property(user => user.LoginErrorCount)
.IsRequired()
.HasComment("登录错误次数");
builder.Property(user => user.CreationTime)
.HasComment("创建时间");
builder.Property(user => user.CreatorId)
.HasComment("创建人");
builder.Property(user => user.LastModificationTime)
.HasComment("更新时间");
builder.Property(user => user.LastModifierId)
.HasComment("更新人");
builder.Property(user => user.DeletionTime)
.HasComment("删除时间");
builder.Property(user => user.DeleterId)
.HasComment("删除人");
builder.Property(user => user.IsDeleted)
.HasComment("是否删除,0:未删除,1:已删除");
builder.HasMany(user => user.UserRoles)
.WithOne()
.HasForeignKey(rel => rel.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
- 用户角色实体映射配置
/// <summary>
/// 用户角色实体映射配置
/// </summary>
public class UserRoleMap : IEntityTypeConfiguration<UserRole>
{
public void Configure(EntityTypeBuilder<UserRole> builder)
{
builder.ToTable(UserConsts.UserRoleTableName, table =>
{
table.HasComment(UserConsts.UserRoleTableComment);
});
builder.ConfigureByConvention();
builder.Property(rel => rel.Id)
.HasComment("主键");
builder.Property(rel => rel.UserId)
.IsRequired()
.HasComment("用户Id");
builder.Property(rel => rel.RoleId)
.IsRequired()
.HasComment("角色Id");
}
}
- 角色实体映射配置
/// <summary>
/// 角色实体映射配置
/// </summary>
public class RoleMap : IEntityTypeConfiguration<Role>
{
public void Configure(EntityTypeBuilder<Role> builder)
{
builder.ToTable(RoleConsts.RoleTableName, table =>
{
table.HasComment(RoleConsts.RoleTableComment);
});
builder.ConfigureByConvention();
builder.Property(role => role.Id)
.HasComment("主键");
builder.Property(role => role.Code)
.HasMaxLength(RoleConsts.MaxCodeLength)
.IsRequired()
.HasComment("编码");
builder.Property(role => role.Name)
.HasMaxLength(RoleConsts.MaxNameLength)
.IsRequired()
.HasComment("名称");
builder.Property(role => role.Remark)
.HasMaxLength(RoleConsts.MaxRemarkLength)
.HasComment("备注");
builder.Property(role => role.CreationTime)
.HasComment("创建时间");
builder.Property(role => role.CreatorId)
.HasComment("创建人");
builder.Property(role => role.LastModificationTime)
.HasComment("更新时间");
builder.Property(role => role.LastModifierId)
.HasComment("更新人");
builder.Property(role => role.DeletionTime)
.HasComment("删除时间");
builder.Property(role => role.DeleterId)
.HasComment("删除人");
builder.Property(role => role.IsDeleted)
.HasComment("是否删除,0:未删除,1:已删除");
builder.HasMany(role => role.RoleMenus)
.WithOne()
.HasForeignKey(rel => rel.RoleId)
.OnDelete(DeleteBehavior.Cascade);
}
}
- 角色菜单实体映射配置
/// <summary>
/// 角色菜单实体映射配置
/// </summary>
public class RoleMenuMap : IEntityTypeConfiguration<RoleMenu>
{
public void Configure(EntityTypeBuilder<RoleMenu> builder)
{
builder.ToTable(RoleConsts.RoleMenuTableName, table =>
{
table.HasComment(RoleConsts.RoleMenuTableComment);
});
builder.ConfigureByConvention();
builder.Property(rel => rel.Id)
.HasComment("主键");
builder.Property(rel => rel.RoleId)
.IsRequired()
.HasComment("角色Id");
builder.Property(rel => rel.MenuCode)
.HasMaxLength(MenuConsts.MaxCodeLength)
.IsRequired()
.HasComment("菜单编码");
builder.Property(rel => rel.CreationTime)
.HasComment("创建时间");
}
}
- 菜单实体映射配置
/// <summary>
/// 菜单实体映射配置
/// </summary>
public class MenuMap : IEntityTypeConfiguration<Menu>
{
public void Configure(EntityTypeBuilder<Menu> builder)
{
builder.ToTable(MenuConsts.MenuTableName, table =>
{
table.HasComment(MenuConsts.MenuTableComment);
});
builder.ConfigureByConvention();
builder.Property(menu => menu.Code)
.HasMaxLength(MenuConsts.MaxCodeLength)
.IsRequired()
.HasComment("编码");
builder.Property(menu => menu.ParentCode)
.HasMaxLength(MenuConsts.MaxParentCodeLength)
.IsRequired()
.HasComment("父编码");
builder.Property(menu => menu.Name)
.HasMaxLength(MenuConsts.MaxNameLength)
.IsRequired()
.HasComment("名称");
builder.Property(menu => menu.Type)
.HasMaxLength(MenuConsts.MaxTypeLength)
.IsRequired()
.HasComment("类型");
builder.Property(menu => menu.Level)
.IsRequired()
.HasComment("层级");
builder.Property(menu => menu.Icon)
.HasMaxLength(MenuConsts.MaxIconLength)
.HasComment("图标");
builder.Property(menu => menu.UrlAddress)
.HasMaxLength(MenuConsts.MaxUrlAddressLength)
.HasComment("路由地址");
builder.Property(menu => menu.ComponentAddress)
.HasMaxLength(MenuConsts.MaxComponentAddressLength)
.HasComment("组件地址");
builder.Property(menu => menu.Sort)
.IsRequired()
.HasComment("排序");
builder.Property(menu => menu.CreationTime)
.HasComment("创建时间");
builder.Ignore(menu => menu.SubMenu);
}
}
仓储接口
在General.Backend.Domain类库中新建名称为IRepositories的文件夹,用于存放业务仓储接口
新建名称为IBaseRepository的基础仓储接口,用于定义仓储通用的功能
/// <summary>
/// 基础仓储
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TKey"></typeparam>
public interface IBaseRepository<TEntity, TKey> : IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>
{
}
新建名称为IUserRepository、IRoleRepository和IMenuRepository的用户仓储、角色仓储和菜单仓储接口
/// <summary>
/// 用户仓储
/// </summary>
public interface IUserRepository : IBaseRepository<User, Guid>
{
}
/// <summary>
/// 角色仓储
/// </summary>
public interface IRoleRepository : IBaseRepository<Role, Guid>
{
}
/// <summary>
/// 菜单仓储
/// </summary>
public interface IMenuRepository : IRepository<Menu, Guid>
{
}
仓储实现
在General.Backend.EntityFrameworkCore类库中新建名称为Repositories的文件夹,用于存放业务仓储实现
新建名称为BaseRepository的基础仓储类,实现仓储通用的功能
/// <summary>
/// 基础仓储
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TKey"></typeparam>
public class BaseRepository<TEntity, TKey> : EfCoreRepository<GeneralDbContext, TEntity, TKey> where TEntity : class, IEntity<TKey>
{
public BaseRepository(IDbContextProvider<GeneralDbContext> dbContextProvider) : base(dbContextProvider)
{
}
}
新建名称为UserRepository、RoleRepository和MenuRepository的用户仓储类、角色仓储类和菜单仓储类
/// <summary>
/// 用户仓储
/// </summary>
public class UserRepository : BaseRepository<User, Guid>, IUserRepository
{
public UserRepository(IDbContextProvider<StoreDbContext> dbContextProvider) : base(dbContextProvider)
{
}
public override async Task<IQueryable<User>> WithDetailsAsync()
{
return (await GetQueryableAsync()).IncludeDetails();
}
}
/// <summary>
/// 角色仓储
/// </summary>
public class RoleRepository : BaseRepository<Role, Guid>, IRoleRepository
{
public RoleRepository(IDbContextProvider<StoreDbContext> dbContextProvider) : base(dbContextProvider)
{
}
public override async Task<IQueryable<Role>> WithDetailsAsync()
{
return (await GetQueryableAsync()).IncludeDetails();
}
}
/// <summary>
/// 菜单仓储
/// </summary>
public class MenuRepository : BaseRepository<Menu, Guid>, IMenuRepository
{
public MenuRepository(IDbContextProvider<StoreDbContext> dbContextProvider) : base(dbContextProvider)
{
}
}
在Repositories文件夹中新建名称为Extensions的文件夹,用于存放业务仓储扩展类
新建名称为UserRepositoryExtensions的用户仓储扩展类,指定查询用户聚合根(包含用户角色实体)
public static class UserRepositoryExtensions
{
public static IQueryable<User> IncludeDetails(
this IQueryable<User> queryable)
{
return queryable.Include(user => user.UserRoles);
}
}
新建名称为RoleRepositoryExtensions的角色仓储扩展类,指定查询角色聚合根(包含角色菜单实体)
public static class RoleRepositoryExtensions
{
public static IQueryable<Role> IncludeDetails(
this IQueryable<Role> queryable)
{
return queryable.Include(role => role.RoleMenus);
}
}
解决方案的目录结构现如下
在下一章节中,实现领域层中的领域服务
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异