向 API层 提供各个实体的数据查询的 IQueryable 类型的数据源

OSharpNS全称OSharp Framework with .NetStandard2.0,是一个基于.NetStandard2.0开发的一个.NetCore快速开发框架。这个框架使用最新稳定版的.NetCore SDK(当前是.NET Core 2.2),对 AspNetCore 的配置、依赖注入、日志、缓存、实体框架、Mvc(WebApi)、身份认证、权限授权等模块进行更高一级的自动化封装,并规范了一套业务实现的代码结构与操作流程,使 .Net Core 框架更易于应用到实际项目开发中

向 API层 提供各个实体的数据查询的 IQueryable 类型的数据源
接收 API层 传入的 IInputDto 参数,完成实体的 新增、更新、删除 等业务操作
接收 API层 传入的参数,处理各种 模块级别 的综合业务
处理完业务之后,将数据通过 数据仓储IRepository 更新到数据库
向 事件总线 模块发布业务处理事件,触发订阅的业务事件
向 API 层返回业务操作结果
整个过程如下图所示:


服务层代码布局
服务层代码布局分析
一个业务模块,是负责完成一系列功能的,这些功能相互之间具有密切的关联性,所以对于一个模块来说,业务服务是一个整体,不应把他们再按单个实体拆分开来。
OSharp 的业务模块代码结构设计,也是根据这一原则来设计的。设计规则如下:

服务接口IBlogsContract:一个模块的业务服务共享一个服务接口,接口中包含模块的综合业务服务,也包含模块的各个实体的查询数据集、新增、更新、删除等自有业务服务。
服务实现BlogsService:服务实现使用 分部类partial 设计,例如本例中的博客模块业务,文件拆分如下:
BlogsService.cs:博客模块服务实现类的主文件,负责各实体的仓储服务注入,辅助服务注入,模块综合业务实现
BlogsService.Blog.cs:博客模块服务的博客实体服务实现类,负责博客实体的 查询数据集、增改删 业务实现
BlogsService.Post.cs:博客模块服务的文章实体服务实现类,负责文章实体的 查询数据集、增改删 业务实现
模块入口BlogsPack:定义模块的级别、启动顺序、执行服务添加、模块初始化等功能
综上,服务层代码布局如下所示:

src                                         # 源代码文件夹
└─Liuliu.Blogs.Core                         # 项目核心工程
   └─Blogs                                  # 博客模块文件夹
        ├─Events                            # 业务事件文件夹
        │    ├─VerifyBlogEventData.cs       # 审核博客事件数据
        │    └─VerifyBlogEventHandler.cs    # 审核博客事件处理器
        ├─BlogsPack.cs                      # 博客模块入口类
        ├─BlogsService.cs                   # 博客服务类
        ├─BlogsService.Blog.cs              # 博客模块-博客服务类
        ├─BlogsService.Post.cs              # 博客模块-文章服务类
        └─IBlogsContract.cs                 # 博客模块服务接口
服务接口 IBlogsContract
接口定义分析
数据查询
对于数据查询,业务层只向 API 层开放一个 IQueryable 的查询数据集。原则上,服务层不实现 纯数据查询(例如 用于列表分页数据、下拉菜单选项 等数据,不涉及数据变更的查询操作) 的服务,所有的 纯数据查询 都在 API层 按需要进行查询。具体分析请看 >>数据查询应该在哪做>>

额外的,根据一定条件判断一个数据是否存在 这种需求经常会用到(例如在新增或修改一个要求唯一的字符串时,需要异步检查输入的字符串是否已存在),因此设计一个 检查实体是否存在CheckEntityExists 的服务很有必要。

!!!node
对于新增、更新、删除操作,除非很确定一次只操作一条记录除外,为了支持可能的批量操作,设计上都应把服务层的 增改删 操作设计为数组型参数的批量操作,同时使用 params 关键字使操作支持单个数据操作。

数据变更
对于每一个实体,服务层按 业务需求分析 的要求定义必要的 新增、更新、删除 等操作,OSharp框架定义了一个 业务操作结果信息类 OperationResult 来封装业务操作结果,这个结果可以返回 操作结果类型(成功/错误/未变化/不存在/验证失败)、返回消息、返回附加数据 等丰富的信息,API层 接受操作结果后可进行相应的处理。

博客模块的接口定义
回到我们的 Liuliu.Blogs 项目,根据 <业务模块设计#服务层> 的需求分析,我们需要给 博客Blog 实体定义 申请开通、开通审核、更新、删除 服务,给 文章Post 实体类定义 新增、更新、删除 服务。

接口定义如下:


///


/// 业务契约接口:博客模块
///

public interface IBlogsContract
{
    #region 博客信息业务

    ///


    /// 获取 博客信息查询数据集
    ///

    IQueryable Blogs { get; }

    ///


    /// 检查博客信息是否存在
    ///

    /// 检查谓语表达式
    /// 更新的博客信息编号
    /// 博客信息是否存在
    Task CheckBlogExists(Expression> predicate, int id = 0);

    ///


    /// 申请博客信息
    ///

    /// 申请博客信息DTO信息
    /// 业务操作结果
    Task ApplyForBlog(BlogInputDto dto);

    ///


    /// 审核博客信息
    ///

    /// 博客编号
    /// 是否通过
    /// 业务操作结果
    Task VerifyBlog(int id, bool isEnabled);

    ///


    /// 更新博客信息
    ///

    /// 包含更新信息的博客信息DTO信息
    /// 业务操作结果
    Task UpdateBlogs(params BlogInputDto[] dtos);

    ///


    /// 删除博客信息
    ///

    /// 要删除的博客信息编号
    /// 业务操作结果
    Task DeleteBlogs(params int[] ids);

    #endregion

    #region 文章信息业务

    ///


    /// 获取 文章信息查询数据集
    ///

    IQueryable Posts { get; }

    ///


    /// 检查文章信息是否存在
    ///

    /// 检查谓语表达式
    /// 更新的文章信息编号
    /// 文章信息是否存在
    Task CheckPostExists(Expression> predicate, int id = 0);

    ///


    /// 添加文章信息
    ///

    /// 要添加的文章信息DTO信息
    /// 业务操作结果
    Task CreatePosts(params PostInputDto[] dtos);

    ///


    /// 更新文章信息
    ///

    /// 包含更新信息的文章信息DTO信息
    /// 业务操作结果
    Task UpdatePosts(params PostInputDto[] dtos);

    ///


    /// 删除文章信息
    ///

    /// 要删除的文章信息编号
    /// 业务操作结果
    Task DeletePosts(params int[] ids);

    #endregion
}
服务实现 BlogsService
依赖服务注入方式分析
服务层的业务实现,通过向服务实现类注入 数据仓储IRepository 对象来获得向数据库存取数据的能力。根据 .NetCore 的依赖注入使用原则,常规的做法是在服务实现类的 构造函数 进行依赖服务的注入。形如:


///


/// 业务服务实现:博客模块
///

public class BlogsService : IBlogsContract
{
    private readonly IRepository _blogRepository;
    private readonly IRepository _postRepository;
    private readonly IRepository _userRepository;
    private readonly IRepository _roleRepository;
    private readonly IRepository _userRoleRepository;
    private readonly IEventBus _eventBus;

    ///


    /// 初始化一个类型的新实例
    ///

    public BlogsService(IRepository blogRepository,
        IRepository postRepository,
        IRepository userRepository,
        IRepository roleRepository,
        IRepository userRoleRepository,
        IEventBus eventBus)
    {
        _blogRepository = blogRepository;
        _postRepository = postRepository;
        _userRepository = userRepository;
        _roleRepository = roleRepository;
        _userRoleRepository = userRoleRepository;
        _eventBus = eventBus;
    }
}
构造函数注入带来的性能影响
每个仓储都使用构造函数注入的话,如果模块的业务比较复杂,涉及的实体比较多(比如十几个实体是很经常的事),就会造成每次实例化 BlogsService 类的实例的时候,都需要去实例化很多个依赖服务,而实际上 一次业务执行只执行服务中的某个方法,可能也就用到其中的一两个依赖服务,这就造成了很多不必要的额外工作,也就是性能损耗。

依赖服务注入的性能优化
如果 不考虑业务服务的可测试性(单元测试通常需要Mock依赖服务)的话,在构造函数中只注入 IServiceProvider 实例,然后在业务代码中使用 serviceProvider.GetService() 的方式来 按需获取 依赖服务的实例,是比较经济的方式。则服务实现变为如下所示:

///


/// 业务服务实现:博客模块
///

public class BlogsService : IBlogsContract
{
    private readonly IServiceProvider _serviceProvider;

    ///


    /// 初始化一个类型的新实例
    ///

    public BlogsService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    ///


    /// 获取 博客仓储对象
    ///

    protected IRepository BlogRepository => _serviceProvider.GetService>();

    ///


    /// 获取 文章仓储对象
    ///

    protected IRepository PostRepository => _serviceProvider.GetService>();

    ///


    /// 获取 用户仓储对象
    ///

    protected IRepository UserRepository => _serviceProvider.GetService>();

    ///


    /// 获取 角色仓储对象
    ///

    protected IRepository RoleRepository => _serviceProvider.GetService>();

    ///


    /// 获取 角色仓储对象
    ///

    protected IRepository UserRoleRepository => _serviceProvider.GetService>();

    ///


    /// 获取 事件总线对象
    ///

    protected IEventBus EventBus => _serviceProvider.GetService();
}
各个依赖服务改为属性的存在方式,并且可访问性为 protected,这就保证了依赖服务的安全性。依赖服务使用 serviceProvider.GetService() 的方式创建实例,可以做到 按需创建,达到性能优化的目的。

增改删操作的简化
常规批量操作的弊端
直接通过 数据仓储IRepository 实现数据的增改删的批量操作,总免不了要使用循环来遍历传进来的多个InputDto,例如文章的更新操作,以下几个步骤是免不了的:

由 dto.Id 查找出相应的文章实体 entity,如果不存在,中止操作并返回
进行更新前的数据检查
检查 dto 的合法性,比如文章标题要求唯一,dto.Title 就要验证唯一性,中止操作并返回
检查 entity 的合法性,比如文章已锁定,就不允许编辑,要进行拦截,检查不通过,中止操作并返回
使用 AutoMapper 将 dto 的值更新到 entity
进行其他关联实体的更新
比如添加文章的编辑记录
比如给当前操作人加积分
将 entity 的更新提交到数据库
整个过程实现代码如下:

///


/// 更新文章信息
///

/// 包含更新信息的文章信息DTO信息
/// 业务操作结果
public virtual async Task UpdatePosts(params PostInputDto[] dtos)
{
    Check.Validate(dtos, nameof(dtos));

    int count = 0;
    foreach (PostInputDto dto in dtos)
    {
        Post entity = await PostRepository.GetAsync(dto.Id);
        if (entity == null)
        {
            return new OperationResult(OperationResultType.QueryNull, $"编号为{dto.Id}的文章信息无法找到");
        }

        // todo:
        // 在这里要检查 dto 的合法性,比如文章标题要求唯一,dto.Title 就要验证唯一性
        // 在这里要检查 entity 的合法性,比如文章已锁定,就不允许编辑,要进行拦截

        entity = dto.MapTo(entity);

        // todo:
        // 在这里要进行其他实体的关联更新,比如添加文章的编辑记录

        count += await PostRepository.UpdateAsync(entity);
    }

    if (count > 0)
    {
        return new OperationResult(OperationResultType.Success, $"{dtos.Length}个文章信息更新成功");
    }
    return OperationResult.NoChanged;
}
批量操作改进
这是个重复性很大的繁琐工作,整个流程中只有第2步和第4步是变化的,其余步骤都相对固定。为了简化这类操作,我们可以将第2、4步骤变化点封装起来,使用 委托 将操作内容作为参数传进来。
OSharp在 数据仓储IRepository 中定义了关于这类 IInputDto 类型参数的实体批量操作API。

例如批量更新,实现如下:

///


/// 异步以DTO为载体批量更新实体
///

/// 更新DTO类型
/// 更新DTO信息集合
/// 更新信息合法性检查委托
/// 由DTO到实体的转换委托
/// 业务操作结果
public virtual async Task UpdateAsync(ICollection dtos,
    Func checkAction = null,
    Func> updateFunc = null) where TEditDto : IInputDto
{
    List names = new List();
    foreach (TEditDto dto in dtos)
    {
        try
        {
            TEntity entity = await _dbSet.FindAsync(dto.Id);
            if (entity == null)
            {
                return new OperationResult(OperationResultType.QueryNull);
            }
            if (checkAction != null)
            {
                await checkAction(dto, entity);
            }
            entity = dto.MapTo(entity);
            if (updateFunc != null)
            {
                entity = await updateFunc(dto, entity);
            }
            entity = CheckUpdate(entity)[0];
            _dbContext.Update(entity);
        }
        catch (OsharpException e)
        {
            return new OperationResult(OperationResultType.Error, e.Message);
        }
        catch (Exception e)
        {
            _logger.LogError(e, e.Message);
            return new OperationResult(OperationResultType.Error, e.Message);
        }
        names.AddIfNotNull(GetNameValue(dto));
    }
    int count = await _dbContext.SaveChangesAsync(_cancellationTokenProvider.Token);
    return count > 0
        ? new OperationResult(OperationResultType.Success,
            names.Count > 0
                ? "信息“{0}”更新成功".FormatWith(names.ExpandAndToString())
                : "{0}个信息更新成功".FormatWith(dtos.Count))
        : new OperationResult(OperationResultType.NoChanged);
}
如上高亮代码,此方法定义了 Func checkAction 和 Func> updateFunc 两个委托参数作为 更新前参数检查 和 更新后关联更新 的操作传入方式,方法中是以 OsharpException 类型异常来作为中止信号的,如果需要在委托中中止操作,直接抛 OsharpException 异常即可。在调用时,即可极大简化批量更新的操作,如上的更新代码,简化如下:

///


/// 更新文章信息
///

/// 包含更新信息的文章信息DTO信息
/// 业务操作结果
public virtual async Task UpdatePosts(params PostInputDto[] dtos)
{
    Check.Validate(dtos, nameof(dtos));

    return await PostRepository.UpdateAsync(dtos, async (dto, entity) =>
        {
            // todo:
            // 在这里要检查 dto 的合法性,比如文章标题要求唯一,dto.Title 就要验证唯一性
            // 在这里要检查 entity 的合法性,比如文章已锁定,就不允许编辑,要进行拦截
        },
        async (dto, entity) =>
        {
            // todo:
            // 在这里要进行其他实体的关联更新,比如添加文章的编辑记录

            return entity;
        });
}
如果没有必要做额外的 更新前检查 和更新后的 关联更新,上面的批量更新可以简化到极致:

///


/// 更新文章信息
///

/// 包含更新信息的文章信息DTO信息
/// 业务操作结果
public virtual async Task UpdatePosts(params PostInputDto[] dtos)
{
    Check.Validate(dtos, nameof(dtos));

    return await PostRepository.UpdateAsync(dtos);
}
服务层的事务管理
事务开启与重用
OSharp的数据层在一次业务处理请求中遇到数据的 新增、更新、删除 操作并第一次执行 SaveChanges 操作时,会自动开启手动事务,以后再次执行 SaveChanges 操作时,会直接使用 同一连接对象 的现有事务,以保证一次业务请求的操作都自在一个事务内。

public override int SaveChanges()
{
    // ...

    //开启或使用现有事务
    BeginOrUseTransaction();

    int count = base.SaveChanges();
    
    // ...

    return count;
}
事务提交
为了方便事务管理和不同的服务层之间的事务同步,OSharp框架默认的事务提交是在 API 层通过 MVC 的 UnitOfWorkAttribute 特性来提交的。

///


/// 新用户注册
///

/// 注册信息
/// JSON操作结果
[HttpPost]
[ServiceFilter(typeof(UnitOfWorkAttribute))]
[ModuleInfo]
[Description("用户注册")]
public async Task Register(RegisterDto dto)
{
    // ...
}
当然,你也可以不在 API 层标注 [UnitOfWorkAttribute],而是在需要的时候通过 IUnitOfWork.Commit() 手动提交事务

IUnitOfWork unitOfWork = HttpContext.RequestServices.GetUnitOfWork();
unitOfWork.Commit();
业务服务事件订阅与发布
业务服务事件,是通过 事件总线EventBus 来实现的,OSharp构建了一个简单的事件总线基础建设,可以很方便地订阅和发布业务事件。

订阅事件
订阅事件很简单,只需要定义一组配套的 事件数据EventData 和相应的 事件处理器EventHandler,即可完成事件订阅的工作。

IEventData
事件数据EventData 是业务服务发布事件时向事件总线传递的数据,每一种业务,都有特定的事件数据,一个事件数据可触发多个事件处理器
定义一个事件数据,需要实现 IEventData 接口

///


/// 定义事件数据,所有事件都要实现该接口
///

public interface IEventData
{
    ///
    /// 获取 事件编号
    ///

    Guid Id { get; }

    ///


    /// 获取 事件发生的时间
    ///

    DateTime EventTime { get; }

    ///


    /// 获取或设置 事件源,触发事件的对象
    ///

    object EventSource { get; set; }
}
EventDataBase
为了方便 事件数据 的定义,OSharp定义了一个通用事件数据基类EventDataBase,继承此基类,只需要添加事件触发需要的业务数据即可

///


/// 事件源数据信息基类
///

public abstract class EventDataBase : IEventData
{
    ///
    /// 初始化一个类型的新实例
    ///

    protected EventDataBase()
    {
        Id = Guid.NewGuid();
        EventTime = DateTime.Now;
    }

    ///


    /// 获取 事件编号
    ///

    public Guid Id { get; }

    ///


    /// 获取 事件发生时间
    ///

    public DateTime EventTime { get; }

    ///


    /// 获取或设置 触发事件的对象
    ///

    public object EventSource { get; set; }
}
IEventHandler
业务事件的处理逻辑,是通过 事件处理器 EventHandler 来实现的,事件处理器应遵从 单一职责 原则,一个处理器只做一件事,业务服务层发布一项 事件数据,可触发多个 事件处理器

///


/// 定义事件处理器,所有事件处理都要实现该接口
/// EventBus中,Handler的调用是同步执行的,如果需要触发就不管的异步执行,可以在实现EventHandler的Handle逻辑时使用Task.Run
///

[IgnoreDependency]
public interface IEventHandler
{
    ///
    /// 是否可处理指定事件
    ///

    /// 事件源数据
    /// 是否可处理
    bool CanHandle(IEventData eventData);

    ///


    /// 事件处理
    ///

    /// 事件源数据
    void Handle(IEventData eventData);

    ///


    /// 异步事件处理
    ///

    /// 事件源数据
    /// 异步取消标识
    ///
    Task HandleAsync(IEventData eventData, CancellationToken cancelToken = default(CancellationToken));
}
泛型事件处理器

///


/// 定义泛型事件处理器
/// EventBus中,Handler的调用是同步执行的,如果需要触发就不管的异步执行,可以在实现EventHandler的Handle逻辑时使用Task.Run
///

/// 事件源数据
[IgnoreDependency]
public interface IEventHandler : IEventHandler where TEventData : IEventData
{
    ///
    /// 事件处理
    ///

    /// 事件源数据
    void Handle(TEventData eventData);

    ///


    /// 异步事件处理
    ///

    /// 事件源数据
    /// 异步取消标识
    Task HandleAsync(TEventData eventData, CancellationToken cancelToken = default(CancellationToken));
}
EventHandlerBase
同样的,为了方便 事件处理器 的定义,OSharp定义了一个通用的事件处理器基类EventHandlerBase,继承此基类,只需要实现核心的事件处理逻辑即可

///


/// 事件处理器基类
///

public abstract class EventHandlerBase : IEventHandler where TEventData : IEventData
{
    ///
    /// 是否可处理指定事件
    ///

    /// 事件源数据
    /// 是否可处理
    public virtual bool CanHandle(IEventData eventData)
    {
        return eventData.GetType() == typeof(TEventData);
    }

    ///


    /// 事件处理
    ///

    /// 事件源数据
    public virtual void Handle(IEventData eventData)
    {
        if (!CanHandle(eventData))
        {
            return;
        }
        Handle((TEventData)eventData);
    }

    ///


    /// 异步事件处理
    ///

    /// 事件源数据
    /// 异步取消标识
    ///
    public virtual Task HandleAsync(IEventData eventData, CancellationToken cancelToken = default(CancellationToken))
    {
        if (!CanHandle(eventData))
        {
            return Task.FromResult(0);
        }
        return HandleAsync((TEventData)eventData, cancelToken);
    }

    ///


    /// 事件处理
    ///

    /// 事件源数据
    public abstract void Handle(TEventData eventData);

    ///


    /// 异步事件处理
    ///

    /// 事件源数据
    /// 异步取消标识
    /// 是否成功
    public virtual Task HandleAsync(TEventData eventData, CancellationToken cancelToken = default(CancellationToken))
    {
        return Task.Run(() => Handle(eventData), cancelToken);
    }
}
发布事件
事件的发布,就相当简单了,只需要实例化一个事件数据EventData的实例,然后通过IEventBus.Publish(eventData)即可发布事件,触发该EventData的所有订阅处理器

XXXEventData eventData = new XXXEventData()
{
    // ...
};
EventBus.Publish(eventData);
博客模块的业务事件实现
回到我们的 Liuliu.Blogs 项目,根据 <业务模块设计#博客业务需求分析> 的需求分析的第二条,审核博客之后需要发邮件通知用户,发邮件属于审核博客业务计划外的需求,使用 业务事件 来实现正当其时。

审核博客业务事件数据
///


/// 审核博客事件数据
///

public class VerifyBlogEventData : EventDataBase
{
    ///
    /// 获取或设置 博客名称
    ///

    public string BlogName { get; set; }

    ///


    /// 获取或设置 用户名
    ///

    public string UserName { get; set; }

    ///


    /// 获取或设置 审核是否通过
    ///

    public bool IsEnabled { get; set; }
}
审核博客业务事件处理器
///
/// 审核博客事件处理器
///

public class VerifyBlogEventHandler : EventHandlerBase
{
    private readonly ILogger _logger;

    ///


    /// 初始化一个类型的新实例
    ///

    public VerifyBlogEventHandler(IServiceProvider serviceProvider)
    {
        _logger = serviceProvider.GetService().CreateLogger();
    }

    ///

事件处理
    /// 事件源数据
    public override void Handle(VerifyBlogEventData eventData)
    {
        _logger.LogInformation(
            $"触发 审核博客事件处理器,用户“{eventData.UserName}”的博客“{eventData.BlogName}”审核结果:{(eventData.IsEnabled ? "通过" : "未通过")}");
    }
}
博客模块的服务实现
回到我们的 Liuliu.Blogs 项目,根据 <业务模块设计#服务层> 的需求分析,综合使用OSharp框架提供的基础建设,博客模块的业务服务实现如下:

BlogsService.cs
///


/// 业务服务实现:博客模块
///

public partial class BlogsService : IBlogsContract
{
    private readonly IServiceProvider _serviceProvider;

    ///


    /// 初始化一个类型的新实例
    ///

    public BlogsService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    ///


    /// 获取 博客仓储对象
    ///

    protected IRepository BlogRepository =www.dell-case.com> _serviceProvider.GetService>();

    ///


    /// 获取 文章仓储对象
    ///

    protected IRepository PostRepository => _serviceProvider.GetService>();

    ///


    /// 获取 用户仓储对象
    ///

    protected IRepository UserRepository => _serviceProvider.GetService>();
}
BlogsService.Blog.cs
public partial class BlogsService
{
    ///
    /// 获取 博客信息查询数据集
    ///

    public virtual IQueryable Blogs => BlogRepository.Query(www.cqyland.com );

    ///


    /// 检查博客信息是否存在
    ///

    /// 检查谓语表达式
    /// 更新的博客信息编号
    /// 博客信息是否存在
    public virtual Task CheckBlogExists(Expression> predicate, int id = 0)
    {
        Check.NotNull(predicate, nameof(predicate)www.chenghgongs.com);
        return BlogRepository.CheckExistsAsync(predicate, id);
    }

    ///


    /// 申请博客信息
    ///

    /// 申请博客信息DTO信息
    /// 业务操作结果
    public virtual async Task ApplyForBlog(BlogInputDto dto)
    {
        Check.Validate(dto, nameof(dto)www.hnxinhe.cn);

        // 博客是以当前用户的身份来申请的
        ClaimsPrincipal principal = _serviceProvider.GetCurrentUser();
        if (principal == null || !principal.Identity.IsAuthenticated)
        {
            return new OperationResult(OperationResultType.Error, "用户未登录或登录已失效");
        }

        int userId = principal.Identity.GetUserId();
        User user = await UserRepository.GetAsync(userId);
        if (user == null)
        {
            return new OperationResult(OperationResultType.QueryNull, $"编号为“{userId}”的用户信息不存在");
        }
        Blog blog = BlogRepository.TrackQuery(m => m.UserId == userId).FirstOrDefault();
        if (blog != null)
        {
            return new OperationResult(OperationResultType.Error, "当前用户已开通博客,不能重复申请");
        }

        if (await CheckBlogExists(m => m.Url == dto.Url))
        {
            return new OperationResult(OperationResultType.Error, $"Url 为“{dto.Url}”的博客已存在,不能重复添加");
        }
        blog = dto.MapTo();
        blog.UserId = userId;
        int count = await BlogRepository.InsertAsync(blog);
        return count > 0
            ? new OperationResult(OperationResultType.Success, "博客申请成功")
            : OperationResult.NoChanged;
    }

    ///


    /// 审核博客信息
    ///

    /// 博客编号
    /// 是否通过
    /// 业务操作结果
    public virtual async Task VerifyBlog(int id, bool isEnabled)
    {
        Blog blog = await BlogRepository.GetAsync(www.yunyouuyL.com );
        if (blog == null)
        {
            return new OperationResult(OperationResultType.QueryNull, $"编号为“{id}”的博客信息不存在");
        }

        // 更新博客
        blog.IsEnabled = isEnabled;
        int count = await BlogRepository.UpdateAsync(blog);

        User user = await UserRepository.GetAsync(blog.UserId);
        if (user == null)
        {
            return new OperationResult(OperationResultType.QueryNull, $"编号为“{blog.UserId}”的用户信息不存在");
        }

        // 如果开通博客,给用户开通博主身份
        if (isEnabled)
        {
            // 查找博客主的角色,博主角色名可由配置系统获得
            const string roleName = "博主";
            // 用于CUD操作的实体,要用 TrackQuery 方法来查询出需要的数据,不能用 Query,因为 Query 会使用 AsNoTracking
            Role role = RoleRepository.TrackQuery(m => m.Name == roleName).FirstOrDefault();
            if (role == null)
            {
                return new OperationResult(OperationResultType.QueryNull, $"名称为“{roleName}”的角色信息不存在");
            }

            UserRole userRole = UserRoleRepository.TrackQuery(m => m.UserId == user.Id && m.RoleId == role.Id)
                .FirstOrDefault();
            if (userRole == null)
            {
                userRole = new UserRole(www.baihuiyulegw.com) { UserId = user.Id, RoleId = role.Id, IsLocked = false };
                count += await UserRoleRepository.InsertAsync(userRole);
            }
        }

        OperationResult result = count > 0
            ? new OperationResult(OperationResultType.Success, $"博客“{blog.Display}”审核 {(isEnabled ? "通过" : "未通过")}")
            : OperationResult.NoChanged;
        if (result.Succeeded)
        {
            VerifyBlogEventData eventData = new VerifyBlogEventData()
            {
                BlogName = blog.Display,
                UserName = user.NickName,
                IsEnabled = isEnabled
            };
            EventBus.Publish(eventData);
        }

        return result;
    }

    ///


    /// 更新博客信息
    ///

    /// 包含更新信息的博客信息DTO信息
    /// 业务操作结果
    public virtual Task UpdateBlogs(params BlogInputDto[] dtos)
    {
        return BlogRepository.UpdateAsync(dtos, async (dto, entity) =>
        {
            if (await BlogRepository.CheckExistsAsync(www.furggw.com => m.Url == dto.Url, dto.Id))
            {
                throw new OsharpException($"Url为“{dto.Url}”的博客已存在,不能重复");
            }
        });
    }

    ///


    /// 删除博客信息
    ///

    /// 要删除的博客信息编号
    /// 业务操作结果
    public virtual Task DeleteBlogs(params int[] ids)
    {
        return BlogRepository.DeleteAsync(ids, entity =>
        {
            if (PostRepository.Query(m => m.BlogId == entity.Id).Any())
            {
                throw new OsharpException($"博客“{entity.Display}”中还有文章未删除,请先删除所有文章,再删除博客");
            }
            return Task.FromResult(0);
        });
    }
}
BlogsService.Post.cs
public partial class BlogsService
{
    ///
    /// 获取 文章信息查询数据集
    ///

    public virtual IQueryable Posts => PostRepository.Query();

    ///


    /// 检查文章信息是否存在
    ///

    /// 检查谓语表达式
    /// 更新的文章信息编号
    /// 文章信息是否存在
    public virtual Task CheckPostExists(Expression> predicate, int id = 0)
    {
        Check.NotNull(predicate, nameof(predicate));
        return PostRepository.CheckExistsAsync(www.yunsengyule.com predicate, id);
    }

    ///


    /// 添加文章信息
    ///

    /// 要添加的文章信息DTO信息
    /// 业务操作结果
    public virtual async Task CreatePosts(params PostInputDto[] dtos)
    {
        Check.Validate(dtos, nameof(dtos));
        if (dtos.Length == 0)
        {
            return OperationResult.NoChanged;
        }

        // 文章是以当前用户身份来添加的
        ClaimsPrincipal principal = _serviceProvider.GetCurrentUser();
        if (principal == null || !principal.Identity.IsAuthenticated)
        {
            throw new OsharpException("用户未登录或登录已失效");
        }
        // 检查当前用户的博客状态
        int userId = principal.Identity.GetUserId();
        Blog blog = BlogRepository.TrackQuery(m => m.UserId == userId).FirstOrDefault();
        if (blog == null || !blog.IsEnabled)
        {
            throw new OsharpException("当前用户的博客未开通,无法添加文章");
        }

        // 没有前置检查,checkAction为null
        return await PostRepository.InsertAsync(dtos, null, (dto, entity) =>
        {
            // 给新建的文章关联博客和作者
            entity.BlogId = blog.Id;
            entity.UserId = userId;
            return Task.FromResult(entity);
        });
    }

    ///


    /// 更新文章信息
    ///

    /// 包含更新信息的文章信息DTO信息
    /// 业务操作结果
    public virtual Task UpdatePosts(params PostInputDto[] dtos)
    {
        Check.Validate(dtos, nameof(dtos));

        return PostRepository.UpdateAsync(dtos);
    }

    ///


    /// 删除文章信息
    ///

    /// 要删除的文章信息编号
    /// 业务操作结果
    public virtual Task DeletePosts(params int[] ids)
    {
        Check.NotNull(ids, nameof(ids));
        return PostRepository.DeleteAsync(ids);
    }
}
模块入口 BlogsPack
模块入口基类
非AspNetCore模块基类 OsharpPack
前面多次提到,每个Pack模块都是继承自一个 模块基类OsharpPack,这个基类用于定义 模块初始化UsePack 过程中未涉及 AspNetCore 环境的模块。

///


/// OSharp模块基类
///

public abstract class OsharpPack
{
    ///
    /// 获取 模块级别,级别越小越先启动
    ///

    public virtual PackLevel Level => PackLevel.Business;

    ///


    /// 获取 模块启动顺序,模块启动的顺序先按级别启动,同一级别内部再按此顺序启动,
    /// 级别默认为0,表示无依赖,需要在同级别有依赖顺序的时候,再重写为>0的顺序值
    ///

    public virtual int Order => 0;

    ///


    /// 获取 是否已可用
    ///

    public bool IsEnabled { get; protected set; }

    ///


    /// 将模块服务添加到依赖注入服务容器中
    ///

    /// 依赖注入服务容器
    ///
    public virtual IServiceCollection AddServices(IServiceCollection services)
    {
        return services;
    }

    ///


    /// 应用模块服务
    ///

    /// 服务提供者
    public virtual void UsePack(IServiceProvider provider)
    {
        IsEnabled = true;
    }

    ///


    /// 获取当前模块的依赖模块类型
    ///

    ///
    internal Type[] GetDependPackTypes(Type packType = null)
    {
        // ...
    }
}
模块基类OsharpPack 定义了两个可重写属性:

PackLevel:模块级别,级别越小越先启动
模块级别按 模块 在框架中不同的功能层次,定义了如下几个级别:
///


/// 模块级别,级别越核心,优先启动
///

public enum PackLevel
{
    ///
    /// 核心级别,表示系统的核心模块,
    /// 这些模块不涉及第三方组件,在系统运行中是不可替换的,核心模块将始终加载
    ///

    Core = 1,
    ///
    /// 框架级别,表示涉及第三方组件的基础模块
    ///

    Framework = 10,
    ///
    /// 应用级别,表示涉及应用数据的基础模块
    ///

    Application = 20,
    ///
    /// 业务级别,表示涉及真实业务处理的模块
    ///

    Business = 30
}
Order:级别内模块启动顺序,模块启动的顺序先按级别启动,同一级别内部再按此顺序启动,级别默认为 0,表示无依赖,需要在同级别有依赖顺序的时候,再重写为 >0 的顺序值
同时,模块基类 还定义了两个方法:

AddServices:用于将模块内定义的服务注入到 依赖注入服务容器 中。
UsePack:用于使用服务对当前模块进行初始化。
AspNetCore模块基类 AspOsharpPack
AspOsharpPack 基类继承了 OsharpPack,添加了一个对 IApplicationBuilder 支持的 UsePack 方法,用于实现与 AspNetCore 关联的模块初始化工作,例如 Mvc模块 初始化的时候需要应用中间件:app.UseMvcWithAreaRoute();

///


///  基于AspNetCore环境的Pack模块基类
///

public abstract class AspOsharpPack : OsharpPack
{
    ///
    /// 应用AspNetCore的服务业务
    ///

    /// Asp应用程序构建器
    public virtual void UsePack(IApplicationBuilder app)
    {
        base.UsePack(app.ApplicationServices);
    }
}
博客模块的模块入口实现
回到我们的 Liuliu.Blogs 项目,我们来实现投票模块的模块入口类 BlogsPack:

博客模块属于业务模块,因此 PackLevel 设置为 Business
博客模块的启动顺序无需重写,保持 0 即可
将 博客业务服务 注册到 服务容器中
无甚初始化业务
实现代码如下:

///


/// 博客模块
///

public class BlogsPack : OsharpPack
{
    ///
    /// 获取 模块级别,级别越小越先启动
    ///

    public override PackLevel Level { get; } = PackLevel.Business;

    ///

将模块服务添加到依赖注入服务容器中
    /// 依赖注入服务容器
    ///
    public override IServiceCollection AddServices(IServiceCollection services)
    {
        services.TryAddScoped();

        return services;
    }
}
至此,博客模块的服务层实现完毕。

你可能感兴趣的:(向 API层 提供各个实体的数据查询的 IQueryable 类型的数据源)