1.2、ABP入门

1、官网、用到的技术、特性的讲解

2、下载项目,EF的Code First模式还原数据库,运行项目

3、所有可用的 NuGet 命令

  1. Update-database同步到数据库中

  2. Add-Migration InitialCreate

  https://www.cnblogs.com/Saumterer/p/7605340.html

 

4、架构说明

 

 

  • XXX.Application(应用层):

   进行展现层领域层之间的协调,协调业务对象来执行特定的应用程序的任务。它不包含业务逻辑,主要包含一些模型、abp重要的数据传输DTO,包括数据库映射实体,前端视图模型转实体(Entity)对象。

   一个应用服务方法通常被认为是一个工作单元(Unit of Work),使用一种像AutoMapper这样的工具来进行实体与DTO之间的映射,前端参数传入有限性验证等等。

  • XXX.Core(领域层):

    领域层就是业务层,是一个项目的核心,所有业务规则都应该在领域层实现。包括业务对象和业务规则,这是应用程序的核心层。

  实体(Entity):
    实体代表业务领域的数据和操作,在实践中,通过用来映射成数据库表。
    ABP中所有的实体类都继承自Entity,而Entity实现了IEntity接口;而IEntity接口是一个泛型接口,通过泛型指定主键Id类型,默认的Entity的主键类型是int类型。

  仓储接口(IRepository):
    仓储用来操作数据库进行数据存取。仓储接口在领域层定义,而仓储的实现类应该写在基础设施层。 ABP针对不同的ORM框架对这个接口进行了默认的实现:
      1) 对于EntityFrameworkCore,提供了EfCoreRepositoryBase<TDbContext, TEntity, TPrimaryKey>的泛型版本的实现方式。
      2) 对于NHibernate,提供了NhRepositoryBase<TEntity, TPrimaryKey>的泛型版本的实现方式

  领域服务(Domain service):
    当处理的业务规则跨越两个(及以上)实体时,应该写在领域服务方法里面。

  领域事件(Domain Event):
    在领域层某些特定情况发生时可以触发领域事件,并且在相应地方捕获并处理它们。 

  工作单元(Unit of Work):
    工作单元是一种设计模式,用于维护一个由已经被修改(如增加、删除和更新等)的业务对象组成的列表。它负责协调这些业务对象的持久化工作及并发问题。

  多语言(Localization):
    定义一种语言对应一个文件,把应用中所有需要进行多语言转换的描述,都可以写在这个目录中。
  • XXX.EntityFrameworkCore(基础设施层):

    提供通用技术来支持更高的层。例如基础设施层的仓储(Repository)可通过ORM来实现数据库交互。当在领域层中为定义了仓储接口,应该在基础设施层中实现这些接口。

    可以使用ORM工具,例如EntityFrameworkCore或NHibernate。ABP的基类已经提供了对这两种ORM工具的支持。还有数据迁移等。

  • XXX.Web.Mvc(展现层):

    提供视图界面与用户进行交互操作。

  • XXX.Web.Host:

    这里在abp中主要是提供接口,可以是解决方案内部使用接口,可以是与移动端等其他端口连接的接口。可以自动生成WebAPI接口,供客户端调用;也可以在脚本中进行调用。

 

5、仓储的注意事项

  • 仓储实现类方法中,ABP自动进行数据库连接的开启和关闭。
  • 仓储方法被调用时,数据库连接自动开启事务。
  • 当仓储方法调用另外一个仓储的方法,它们实际上共享的是同一个数据库连接和事务
  • 仓储对象都是暂时性的,因为IRepository接口默认继承自ITransientDependency接口。所以,仓储对象只有在需要注入的时候,才会由Ioc容器自动创建新实例
  • ABP默认的泛型仓储功能满足我们大部分的CURD操作需求。当有默认的仓储功能不满足需求的情况下,可以创建自己定制化的仓储实现类。

 

6、为什么需要通过dto进行数据传输?

  一般来说,使用DTO进行数据传输具有以下好处。

  • 数据隐藏
  • 序列化和延迟加载问题
  • ABP对DTO提供了约定类以支持验证
  • 参数或返回值改变,通过Dto方便扩展
  • DTO类被用来在 基础设施层 和 应用层 传递数据

 

7、Dto规范(灵活应用)

  • ABP建议命名输入/输出参数为:MethodNameInput和MethodNameOutput,并为每个应用服务方法定义单独的输入和输出DTO(如果为每个方法的输入输出都定义一个dto,那将有一个庞大的dto类需要定义维护。一般通过定义一个公用的dto进行共用)
  • 即使你的方法只接受/返回一个参数,也最好是创建一个DTO类
  • 一般会在对应实体的应用服务文件夹下新建Dtos文件夹来管理Dto类。
  • 定义完DTO,是不是脑袋有个疑问,我在用DTO在展现层与应用服务层进行数据传输,但最终这些DTO都需要转换为实体才能与数据库直接打交道啊。如果每个dto都要自己手动去转换成对应实体,这个工作量也是不可小觑啊。

 

8、在Abp中有两种方式创建映射规则:
  特性数据注解方式:

AutoMapFrom、AutoMapTo 特性创建单向映射
AutoMap 特性创建双向映射

  代码创建映射规则:

Mapper.CreateMap<source, destination>();

 

9、种子数据

  Migrations文件夹下有个SeedData文件夹,顾名思义,这个文件夹下的类主要是用来进行预置种子数据的。

  JK.ABP.EntityFrameworkCore.Seed下面添加自己的种子数据,可以模仿默认的类来写,写好之后,在JK.ABP.EntityFrameworkCore.Seed.Host下面的InitialHostDbBuilder中添加创建代码new DefaultJKExCreator(_context).Create();

在程序包管理器控制台,输入Update-Database,回车执行迁移。执行成功后,查看数据库,Tasks表创建成功,且表中已存在两条测试数据


10、工作单元
UnitOfWork

  UnitOfWork还有三种使用方式:过程式、惯例、声明式

  

using(var unitOfWork = _unitOfWorkManager.Begin())
{
  。。。
  unitOfWork.Complete();
}

   

namespace Abp.Domain.Uow
{
  /// <summary>
  /// This class is used to register interceptor for needed classes for Unit Of Work mechanism.
  /// </summary>
  internal static class UnitOfWorkRegistrar
  {
    /// <summary>
    /// Initializes the registerer.
    /// </summary>
    /// <param name="iocManager">IOC manager</param>
    public static void Initialize(IIocManager iocManager)
    {
      iocManager.IocContainer.Kernel.ComponentRegistered += ComponentRegistered; //利用Castle这个IOC的ComponentRegistered事件来注册拦截器
    }
 
    private static void ComponentRegistered(string key, IHandler handler)
    {
      if (UnitOfWorkHelper.IsConventionalUowClass(handler.ComponentModel.Implementation)) //惯例
      {
        //Intercept all methods of all repositories.
        handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
      }
      else if (handler.ComponentModel.Implementation.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(UnitOfWorkHelper.HasUnitOfWorkAttribute))
      {
        //这里就是声明式         //Intercept all methods of classes those have at least one method that has UnitOfWork attribute.         //TODO: Intecept only UnitOfWork methods, not other methods!         handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));       }     }   } }
internal static class UnitOfWorkHelper
{
  /// <summary>
  /// Returns true if UOW must be used for given type as convention.
  /// </summary>
  /// <param name="type">Type to check</param>
  public static bool IsConventionalUowClass(Type type) //惯例
  {
    return typeof(IRepository).IsAssignableFrom(type) || typeof(IApplicationService).IsAssignableFrom(type);
  }

  /// <summary>
  /// Returns true if given method has UnitOfWorkAttribute attribute.
  /// </summary>
  /// <param name="methodInfo">Method info to check</param>
  public static bool HasUnitOfWorkAttribute(MemberInfo methodInfo) //声明
  {
    return methodInfo.IsDefined(typeof(UnitOfWorkAttribute), true);
  }

  ...
}

 

 过程、惯例,都没问题,声明式是要注意的:

声明式的这些限制,其实是由拦截器的实现机制引起的,ABP的拦截器是用Castle DynamicProxy 动态代理来做的,动态代理是在运行时生成(使用.Net emit)一个新类(继承于原类或接口),拦截的method, 都是用override来插入代码的, 所以只能支持Interface或Virtual的方法。

 

总结:ABP中大量使用了AOP(面向切面编程),分离了横切关注点:Authorization, Validation, Exception Handling, Logging, Localization, Database Connection Management, Setting Management, Audit Logging;实现机理是用动态代理做的拦截器, 作为开发者对这个机理的彻底了解,有助于我更好的使用框架,也有助于用类似的方法做我们自己的AOP,毕竟AOP是我辈热衷于OOP的开发者必须掌握的技术!

 

 

 

posted @ 2020-02-29 19:30  殇琉璃  阅读(403)  评论(0编辑  收藏  举报