zoukankan      html  css  js  c++  java
  • [开源]OSharpNS 步步为营系列

    什么是OSharp

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

    概述

    一个模块的数据层,主要包括如下几个方面:

    • 数据实体:数据实体是服务层向数据库进行数据持久化的数据载体,也是数据层进行业务处理的数据载体,数据层与数据层的数据交换都应通过实体类来完成。

    • 输入DTO:InputDto用于前端向API层输入数据,API层向服务层输入数据

    • 输出DTO:OutputDto主要用于数据查询服务,是API层向前端输出数据的载体

    • 数据实体映射配置:CodeFirst的开发模式,数据库的设计细节完全靠代码来完成,数据实体映射配置正是负责这项工作的,针对一个实体,可以在这里配置其在数据库中的数据表关系、数据约束及各个数据字段的每一个细节配置。

    • 对象Mapper映射:数据传输对象DTO 与 实体类Entity 之间的转换与更新,如果都要通过手写代码来进行属性的一一对应赋值,是件很累人的事,通过 对象Mapper映射(例如AutoMapper)功能,只需进行一次配置,即可很方便的实现不同类型对象的数据转换与更新。

    数据层文件夹布局

    src                                     # 源代码文件夹
    ├─Liuliu.Blogs.Core                     # 项目核心工程
    │  └─Blogs                              # 博客模块文件夹
    │      ├─Dtos                           # 博客模块DTO文件夹
    │      │    ├─BlogInputDto.cs           # 博客输入DTO
    │      │    ├─BlogOutputDto.cs          # 博客输出DTO
    │      │    ├─PostInputDto.cs           # 文章输入DTO
    │      │    └─PostOutputDto.cs          # 文章输出DTO
    │      └─Entities                       # 博客模块实体类文件夹
    │           ├─Blog.cs                   # 博客实体类
    │           └─Post.cs                   # 文章实体类
    └─Liuliu.Blogs.EntityConfiguration      # 实体映射配置工程
       └─Blogs                              # 博客模块文件夹
           ├─BlogConfiguration.cs           # 博客实体映射配置类
           └─PostConfiguration.cs           # 文章实体映射配置类
    

    实体类

    必选接口

    IEntity

    数据实体接口:所有的实体类都必须实现 IEntity 接口,此接口给实体类添加一个 名为 Id 的实体唯一编号属性,这个属性是实体类的 主键 属性

    /// <summary>
    /// 数据模型接口
    /// </summary>
    /// <typeparam name="TKey">实体主键类型</typeparam>
    public interface IEntity<out TKey>
    {
        /// <summary>
        /// 获取 实体唯一标识,主键
        /// </summary>
        TKey Id { get; }
    }
    

    EntityBase

    实体类基类:为了方便实体类快速实现 IEntity 接口,定义了一个通用基类 EntityBase,所有实体都可以通过继承此基类方便实现 IEntity 接口

    /// <summary>
    /// 实体类基类
    /// </summary>
    public abstract class EntityBase<TKey> : IEntity<TKey> where TKey : IEquatable<TKey>
    {
        /// <summary>
        /// 初始化一个<see cref="EntityBase{TKey}"/>类型的新实例
        /// </summary>
        protected EntityBase()
        {
            if (typeof(TKey) == typeof(Guid))
            {
                Id = CombGuid.NewGuid().CastTo<TKey>();
            }
        }
    
        /// <summary>
        /// 获取或设置 编号
        /// </summary>
        [DisplayName("编号")]
        public TKey Id { get; set; }
    
        // ...
    }
    

    可选的装配功能接口与特性

    OSharp框架给实体类设计了一些可装配的功能,通过特定的接口来提供,实体类实现了特定的接口,则实体类在系统中将自动拥有一些额外的功能。

    ICreatedTime

    定义创建时间:ICreatedTime 接口给实体类添加一个 创建时间CreatedTime 的属性,该属性将在数据层执行 创建Insert 操作时,自动赋予系统当前时间。

    /// <summary>
    /// 定义创建时间
    /// </summary>
    public interface ICreatedTime
    {
        /// <summary>
        /// 获取或设置 创建时间
        /// </summary>
        DateTime CreatedTime { get; set; }
    }
    

    ILockable

    定义可锁定功能:ILockable 接口给实体类添加一个 是否锁定IsLocked 的属性

    /// <summary>
    /// 定义可锁定功能
    /// </summary>
    public interface ILockable
    {
        /// <summary>
        /// 获取或设置 是否锁定当前信息
        /// </summary>
        bool IsLocked { get; set; }
    }
    

    IEntityHash

    定义实体Hash功能:给实体添加 GetHash 扩展方法,用于对实体的属性值进行Hash,确定实体是否存在变化,这些变化可用于系统初始化时确定是否需要进行数据同步。
    当前系统中 实体信息EntityInfo, 功能点Function, API模块Module 在系统启动时与数据库中的相应数据进行对比同步时,使用的就是这个接口的特性

    /// <summary>
    /// 定义实体Hash功能,对实体的属性值进行Hash,确定实体是否存在变化,
    /// 这些变化可用于系统初始化时确定是否需要进行数据同步
    /// </summary>
    public interface IEntityHash
    { }
    

    IExpirable

    定义可过期性,包含生效时间和过期时间:给实体添加 生效时间BeginTime,过期时间EndTime 的属性

    /// <summary>
    /// 定义可过期性,包含生效时间和过期时间
    /// </summary>
    public interface IExpirable
    {
        /// <summary>
        /// 获取或设置 生效时间
        /// </summary>
        DateTime? BeginTime { get; set; }
    
        /// <summary>
        /// 获取或设置 过期时间
        /// </summary>
        DateTime? EndTime { get; set; }
    }
    

    ICreationAudited

    定义创建审计信息:给实体添加创建时的 创建人CreatorId,创建时间CreatedTime 的审计信息,这些值将在数据层执行 创建Insert 时自动赋值。

    /// <summary>
    /// 定义创建审计信息
    /// </summary>
    public interface ICreationAudited<TUserKey> : ICreatedTime
        where TUserKey : struct
    {
        /// <summary>
        /// 获取或设置 创建者编号
        /// </summary>
        TUserKey? CreatorId { get; set; }
    }
    

    ICreationAudited 已继承了 ICreatedTime 接口,不会重复添加 CreatedTime 属性

    IUpdateAudited

    定义更新审计的信息:给实体添加更新时的 最后更新人LastUpdaterId,最后更新时间LastUpdatedTime 的审计信息,这些值将在数据层执行 更新Update 操作时自动赋值。

    /// <summary>
    /// 定义更新审计的信息
    /// </summary>
    public interface IUpdateAudited<TUserKey> where TUserKey : struct
    {
        /// <summary>
        /// 获取或设置 更新者编号
        /// </summary>
        TUserKey? LastUpdaterId { get; set; }
    
        /// <summary>
        /// 获取或设置 最后更新时间
        /// </summary>
        DateTime? LastUpdatedTime { get; set; }
    }
    

    ISoftDeletable

    定义逻辑删除功能:给实体添加一个用于逻辑删除的 删除时间DeletedTime 属性,当实体实现了 ISoftDeletable 接口之后,数据层执行 删除 Delete 操作时,将 DeletedTime 赋予当前时间,即表示数据已被删除。数据查询的时候通过全局过滤器自动过滤掉 DeletedTime 不为 null 的数据,从而查询出正常数据。

    /// <summary>
    /// 定义逻辑删除功能
    /// </summary>
    public interface ISoftDeletable
    {
        /// <summary>
        /// 获取或设置 数据逻辑删除时间,为null表示正常数据,有值表示已逻辑删除,同时删除时间每次不同也能保证索引唯一性
        /// </summary>
        DateTime? DeletedTime { get; set; }
    }
    

    UserFlagAttribute

    用户标记特性:此特性与数据权限有关,用于实体类的 用户编号UserId 属性,用于标记实体类中与用户私有数据相关联的用户编号,以在数据权限判断时,将实体的用户编号与当前在线用户的用户编号进行对比,找出用户的私有数据。

        /// <summary>
        /// 用户标记,用于标示用户属性/字段
        /// </summary>
        [AttributeUsage(AttributeTargets.Property)]
        public class UserFlagAttribute : Attribute
        { }
    

    博客模块的实体类定义

    回到我们的 Liuliu.Blogs 项目,根据 业务模块设计#模块文件夹结构布局给出的结构,在 Liuliu.Blogs.Core 项目中创建 Blogs/Entities 的文件夹存放实体类,添加如下实体类文件。

    博客实体 - Blog

    namespace Liuliu.Blogs.Blogs.Entities
    {
        /// <summary>
        /// 实体类:博客信息
        /// </summary>
        public class Blog : EntityBase<int>, ICreatedTime, ISoftDeletable
        {
            /// <summary>
            /// 获取或设置 博客地址
            /// </summary>
            [Required]
            public string Url { get; set; }
    
            /// <summary>
            /// 获取或设置 显示名称
            /// </summary>
            [Required]
            public string Display { get; set; }
    
            /// <summary>
            /// 获取或设置 已开通
            /// </summary>
            public bool IsEnabled { get; set; }
    
            /// <summary>
            /// 获取或设置 创建时间
            /// </summary>
            public DateTime CreatedTime { get; set; }
    
            /// <summary>
            /// 获取或设置 数据逻辑删除时间,为null表示正常数据,有值表示已逻辑删除
            /// </summary>
            public DateTime? DeletedTime { get; set; }
    
            /// <summary>
            /// 获取或设置 作者编号
            /// </summary>
            [UserFlag]
            public int UserId { get; set; }
    
            /// <summary>
            /// 获取或设置 作者
            /// </summary>
            public virtual User User { get; set; }
        }
    }
    

    文章实体 - Post

    namespace Liuliu.Blogs.Blogs.Entities
    {
        /// <summary>
        /// 实体类:文章信息
        /// </summary>
        public class Post:EntityBase<int>,ICreatedTime,ISoftDeletable
        {
            /// <summary>
            /// 获取或设置 文章标题
            /// </summary>
            [Required]
            public string Title { get; set; }
    
            /// <summary>
            /// 获取或设置 文章内容
            /// </summary>
            [Required]
            public string Content { get; set; }
    
            /// <summary>
            /// 获取或设置 创建时间
            /// </summary>
            public DateTime CreatedTime { get; set; }
    
            /// <summary>
            /// 获取或设置 数据逻辑删除时间,为null表示正常数据,有值表示已逻辑删除
            /// </summary>
            public DateTime? DeletedTime { get; set; }
    
            /// <summary>
            /// 获取或设置 所属博客编号
            /// </summary>
            public int BlogId { get; set; }
    
            /// <summary>
            /// 获取或设置 所属博客
            /// </summary>
            public virtual Blog Blog { get; set; }
    
            /// <summary>
            /// 获取或设置 作者编号
            /// </summary>
            [UserFlag]
            public int UserId { get; set; }
    
            /// <summary>
            /// 获取或设置 作者
            /// </summary>
            public virtual User User { get; set; }
        }
    }
    

    输入输出DTO

    输入输出DTO:输入输出DTO主要负责各层次之间的数据传输工作,避免在外层暴露实体类。

    输入输出DTO相关接口

    InputDto

    定义输入DTO:输入DTO 需实现此接口,作为 业务层参数类型、DTO属性合法性验证数据类型 的约束。

    /// <summary>
    /// 定义输入DTO
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    public interface IInputDto<TKey>
    {
        /// <summary>
        /// 获取或设置 主键,唯一标识
        /// </summary>
        TKey Id { get; set; }
    }
    

    IOutputDto

    定义输出DTO:输出DTO 需实现此接口,此接口将作为 分页查询 返回类型的约束。

        /// <summary>
        /// 定义输出DTO
        /// </summary>
        public interface IOutputDto
        { }
    

    数据实体映射

    定义数据权限的更新,删除状态:用于 输入DTOIOutputDto,此接口为输出DTO提供 是否允许更新Updatable、是否允许删除Deletable 两个数据权限属性,输出DTO实现了此接口,在进行数据分页查询时,将使用 全实体 查询模式并进行 数据权限校验,在输出DTO中自动附加数据权限(是否可更新、是否可删除)信息。

        /// <summary>
        /// 定义数据权限的更新,删除状态
        /// </summary>
        public interface IDataAuthEnabled
        {
            /// <summary>
            /// 获取或设置 是否可更新的数据权限状态
            /// </summary>
            bool Updatable { get; set; }
    
            /// <summary>
            /// 获取或设置 是否可删除的数据权限状态
            /// </summary>
            bool Deletable { get; set; }
        }
    

    博客模块的输入输出DTO类定义

    回到我们的 Liuliu.Blogs 项目,根据 业务模块设计#模块文件夹结构布局给出的结构,在 Liuliu.Blogs.Core 项目中创建 Blogs/Dtos 的文件夹存放实体类,添加如下输入输出DTO类文件。

    博客 - Blog

    博客 InputDto

    namespace Liuliu.Blogs.Blogs.Dtos
    {
        /// <summary>
        /// 输入DTO:博客信息
        /// </summary>
        public class BlogInputDto : IInputDto<int>
        {
            /// <summary>
            /// 获取或设置 博客编号
            /// </summary>
            public int Id { get; set; }
    
            /// <summary>
            /// 获取或设置 博客地址
            /// </summary>
            [Required]
            public string Url { get; set; }
    
            /// <summary>
            /// 获取或设置 显示名称
            /// </summary>
            [Required]
            public string Display { get; set; }
        }
    }
    

    博客 OutputDto

    namespace Liuliu.Blogs.Blogs.Dtos
    {
        /// <summary>
        /// 输出DTO:博客信息
        /// </summary>
        public class BlogOutputDto : IOutputDto, IDataAuthEnabled
        {
            /// <summary>
            /// 获取或设置 博客编号
            /// </summary>
            public int Id { get; set; }
    
            /// <summary>
            /// 获取或设置 博客地址
            /// </summary>
            public string Url { get; set; }
    
            /// <summary>
            /// 获取或设置 显示名称
            /// </summary>
            public string Display { get; set; }
    
            /// <summary>
            /// 获取或设置 已开通
            /// </summary>
            public bool IsEnabled { get; set; }
    
            /// <summary>
            /// 获取或设置 创建时间
            /// </summary>
            public DateTime CreatedTime { get; set; }
    
            /// <summary>
            /// 获取或设置 作者编号
            /// </summary>
            public int UserId { get; set; }
    
            /// <summary>
            /// 获取或设置 是否可更新的数据权限状态
            /// </summary>
            public bool Updatable { get; set; }
    
            /// <summary>
            /// 获取或设置 是否可删除的数据权限状态
            /// </summary>
            public bool Deletable { get; set; }
        }
    }
    

    文章 - Post

    文章 InputDto

    namespace Liuliu.Blogs.Blogs.Dtos
    {
        /// <summary>
        /// 输入DTO:文章信息
        /// </summary>
        public class PostInputDto : IInputDto<int>
        {
            /// <summary>
            /// 获取或设置 文章编号
            /// </summary>
            public int Id { get; set; }
    
            /// <summary>
            /// 获取或设置 文章标题
            /// </summary>
            [Required]
            public string Title { get; set; }
    
            /// <summary>
            /// 获取或设置 文章内容
            /// </summary>
            [Required]
            public string Content { get; set; }
        }
    }
    

    文章 OutputDto

    namespace Liuliu.Blogs.Blogs.Dtos
    {
        /// <summary>
        /// 输出DTO:文章信息
        /// </summary>
        public class PostOutputDto : IOutputDto, IDataAuthEnabled
        {
            /// <summary>
            /// 获取或设置 文章编号
            /// </summary>
            public int Id { get; set; }
    
            /// <summary>
            /// 获取或设置 文章标题
            /// </summary>
            public string Title { get; set; }
    
            /// <summary>
            /// 获取或设置 文章内容
            /// </summary>
            public string Content { get; set; }
    
            /// <summary>
            /// 获取或设置 创建时间
            /// </summary>
            public DateTime CreatedTime { get; set; }
    
            /// <summary>
            /// 获取或设置 所属博客编号
            /// </summary>
            public int BlogId { get; set; }
    
            /// <summary>
            /// 获取或设置 作者编号
            /// </summary>
            [UserFlag]
            public int UserId { get; set; }
    
            /// <summary>
            /// 获取或设置 是否可更新的数据权限状态
            /// </summary>
            public bool Updatable { get; set; }
    
            /// <summary>
            /// 获取或设置 是否可删除的数据权限状态
            /// </summary>
            public bool Deletable { get; set; }
        }
    }
    

    数据实体映射配置

    OSharp框架中,实体映射配置类主要有两个作用:

    1. 在系统初始化时将实体类加载到上下文中
    2. 实现实体类到数据库的映射细节配置

    相关接口与基类

    IEntityRegister

    将实体配置类注册到上下文中:IEntityRegister 接口定义了一个DbContextType属性,可将一个实体类注册到指定的上下文中,这在自定义多个数据上下文的时候会用到。
    在实体映射类中实现此接口,数据上下文初始化的时候,在OnModelCreating方法中通过调用RegisterTo(ModelBuilder modelBuilder)方法,将实体映射类的实例注册到ModelBuilder中。

    /// <summary>
    /// 定义将实体配置类注册到上下文中
    /// </summary>
    public interface IEntityRegister
    {
        /// <summary>
        /// 获取所属的上下文类型,如为null,将使用默认上下文,否则使用指定类型的上下文类型
        /// </summary>
        Type DbContextType { get; }
    
        /// <summary>
        /// 获取 相应的实体类型
        /// </summary>
        Type EntityType { get; }
    
        /// <summary>
        /// 将当前实体类映射对象注册到数据上下文模型构建器中
        /// </summary>
        /// <param name="modelBuilder">上下文模型构建器</param>
        void RegisterTo(ModelBuilder modelBuilder);
    }
    

    EntityTypeConfigurationBase

    数据实体映射配置基类:为方便实现实体映射配置类,OSharp定义了此基类,实现了 IEntityTypeConfiguration<TEntity>IEntityRegister 两个接口,在实现实体映射配置类时,只需继承此类,实现Configure(EntityTypeBuilder<TEntity> builder)抽象方法即可。

    /// <summary>
    /// 数据实体映射配置基类
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <typeparam name="TKey">主键类型</typeparam>
    public abstract class EntityTypeConfigurationBase<TEntity, TKey> : IEntityTypeConfiguration<TEntity>, IEntityRegister
        where TEntity : class, IEntity<TKey>
        where TKey : IEquatable<TKey>
    {
        /// <summary>
        /// 获取 所属的上下文类型,如为null,将使用默认上下文, 否则使用指定类型的上下文类型
        /// </summary>
        public virtual Type DbContextType => null;
    
        /// <summary>
        /// 获取 相应的实体类型
        /// </summary>
        public Type EntityType => typeof(TEntity);
    
        /// <summary>
        /// 将当前实体类映射对象注册到数据上下文模型构建器中
        /// </summary>
        /// <param name="modelBuilder">上下文模型构建器</param>
        public void RegisterTo(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(this);
            // 给软删除的实体添加全局过滤器
            if (typeof(ISoftDeletable).IsAssignableFrom(typeof(TEntity)))
            {
                modelBuilder.Entity<TEntity>().HasQueryFilter(m => ((ISoftDeletable)m).DeletedTime == null);
            }
        }
    
        /// <summary>
        /// 重写以实现实体类型各个属性的数据库配置
        /// </summary>
        /// <param name="builder">实体类型创建器</param>
        public abstract void Configure(EntityTypeBuilder<TEntity> builder);
    }
    

    博客模块的实体映射配置类实现

    回到我们的 Liuliu.Blogs 项目,根据 业务模块设计#模块文件夹结构布局给出的结构,在 Liuliu.Blogs.EntityConfiguration 项目中创建 Blogs 的文件夹存放实体类,添加如下实体映射配置类文件。

    博客 - Blog

    根据 业务模块设计#数据层的定义,博客实体的设计要求Url属性 唯一索引,并且 博客与博主 之间的关系是 一对一,因此做如下约束:

    1. 创建Url属性的唯一索引,形式本应是builder.HasIndex(m => m.Url),但博客实体引入了逻辑删除,因此唯一索引应加入逻辑删除属性DeletedTime
    2. 博客实体与用户实体之间以UserId为外键,建立一对一关系,并且对于一个博客来说,其拥有者是必须的,因此需要加上IsRequired约束,并禁止级联删除。
    /// <summary>
    /// 实体映射配置类:博客信息
    /// </summary>
    public class BlogConfiguration : EntityTypeConfigurationBase<Blog, int>
    {
        /// <summary>
        /// 重写以实现实体类型各个属性的数据库配置
        /// </summary>
        /// <param name="builder">实体类型创建器</param>
        public override void Configure(EntityTypeBuilder<Blog> builder)
        {
            builder.HasIndex(m => new {m.Url, m.DeletedTime}).HasName("BlogUrlIndex").IsUnique();
            builder.HasOne(m => m.User).WithOne().HasForeignKey<Blog>(m => m.UserId).OnDelete(DeleteBehavior.Restrict).IsRequired();
        }
    }
    

    文章 - Post

    根据 业务模块设计#数据层的定义,文章实体的设计要求 文章与博客 之间的关系是 多对一文章与作者 之间的关系是 多对一,因此做如下约束:

    1. 文章实体与博客实体之间以BlogId为外键,建立多对一关系,并且对于一篇文章来说,其所在博客是必须的,因此需要加上IsRequired约束,并禁止级联删除。
    2. 文章实体与用户实体之间以UserId为外键,建立多对一关系,并且对于一篇文章来说,其作者是必须的,因此需要加上IsRequired约束,并禁止级联删除。
        /// <summary>
        /// 实体映射配置类:文章信息
        /// </summary>
        public class PostConfiguration : EntityTypeConfigurationBase<Post, int>
        {
            /// <summary>
            /// 重写以实现实体类型各个属性的数据库配置
            /// </summary>
            /// <param name="builder">实体类型创建器</param>
            public override void Configure(EntityTypeBuilder<Post> builder)
            {
                builder.HasOne(m => m.Blog).WithMany().HasForeignKey(m => m.BlogId).OnDelete(DeleteBehavior.Restrict).IsRequired();
                builder.HasOne(m => m.User).WithMany().HasForeignKey(m => m.UserId).OnDelete(DeleteBehavior.Restrict).IsRequired();
            }
        }
    

    对象Mapper映射

    对象Mapper映射,指的是同一实体数据的不同类型的相同属性(亦可是不同属性之间,需要单独配置)之间的数据赋值与更新操作。例如:

    • 新增业务中使用 InputDto 创建 新的实体类
    • 更新业务中通过 InputDto 更新 已存在的实体类
    • 数据查询中将 实体类 转换为指定的 OutputDto
    • 其他不同类型的数据之间的数据赋值与更新

    对象Mapper映射相关基础建设

    OSharp框架中定义了一些基础建设代码,用于定义对象映射功能。

    IMapper

    IMapper 接口用于定义对象映射功能,业务代码中依赖此接口实现对象映射工作,系统初始化时,将使用具体的对象映射框架(如 AutoMapper,``)

    /// <summary>
    /// 定义对象映射功能
    /// </summary>
    public interface IMapper
    {
        /// <summary>
        /// 将对象映射为指定类型
        /// </summary>
        /// <typeparam name="TTarget">要映射的目标类型</typeparam>
        /// <param name="source">源对象</param>
        /// <returns>目标类型的对象</returns>
        TTarget MapTo<TTarget>(object source);
    
        /// <summary>
        /// 使用源类型的对象更新目标类型的对象
        /// </summary>
        /// <typeparam name="TSource">源类型</typeparam>
        /// <typeparam name="TTarget">目标类型</typeparam>
        /// <param name="source">源对象</param>
        /// <param name="target">待更新的目标对象</param>
        /// <returns>更新后的目标类型对象</returns>
        TTarget MapTo<TSource, TTarget>(TSource source, TTarget target);
    
        /// <summary>
        /// 将数据源映射为指定输出DTO的集合
        /// </summary>
        /// <typeparam name="TOutputDto">输出DTO类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="membersToExpand">成员展开</param>
        /// <returns>输出DTO的结果集</returns>
        IQueryable<TOutputDto> ToOutput<TOutputDto>(IQueryable source, params Expression<Func<TOutputDto, object>>[] membersToExpand);
    }
    

    MapFromAttribute,MapToAttribute

    MapFromMapTo两个特性用于类型的 同类型同名属性 的简单映射,不能支持复杂属性间的映射关系。

    • [MapFrom]:标注从源类型当前类型的Mapping映射关系,例如在 OutputDto 上标注[MapFrom(typeof(Entity))],即表示OutputDto是从Entity映射而来的
    /// <summary>
    /// 标注当前类型从源类型的Mapping映射关系
    /// </summary>
    public class MapFromAttribute : Attribute
    {
        /// <summary>
        /// 初始化一个<see cref="MapFromAttribute"/>类型的新实例
        /// </summary>
        public MapFromAttribute(params Type[] sourceTypes)
        {
            Check.NotNull(sourceTypes, nameof(sourceTypes));
            SourceTypes = sourceTypes;
        }
    
        /// <summary>
        /// 源类型
        /// </summary>
        public Type[] SourceTypes { get; }
    }
    
    • [MapTo]:标注从当前类型目标类型的Mapping映射关系,例如在 Input 上标注[MapTo(typeof(Entity))],即表示可从InputDto通过映射创建或更新Entity
    /// <summary>
    /// 标注当前类型映射到目标类型的Mapping映射关系
    /// </summary>
    public class MapToAttribute : Attribute
    {
        /// <summary>
        /// 初始化一个<see cref="MapToAttribute"/>类型的新实例
        /// </summary>
        public MapToAttribute(params Type[] targetTypes)
        {
            Check.NotNull(targetTypes, nameof(targetTypes));
            TargetTypes = targetTypes;
        }
    
        /// <summary>
        /// 目标类型
        /// </summary>
        public Type[] TargetTypes { get; }
    }
    

    IAutoMapperConfiguration

    在使用AutoMapper的场景下,要实现复杂属性的映射配置,需要实现此接口并通过MapperConfigurationExpression配置复杂类型间的属性映射关系

    /// <summary>
    /// 定义通过<see cref="MapperConfigurationExpression"/>配置对象映射的功能
    /// </summary>
    [MultipleDependency]
    public interface IAutoMapperConfiguration
    {
        /// <summary>
        /// 创建对象映射
        /// </summary>
        /// <param name="mapper">映射配置表达</param>
        void CreateMaps(MapperConfigurationExpression mapper);
    }
    

    博客模块的映射关系配置

    回到我们的 Liuliu.Blogs 项目,博客实体和文章实体与他们的DTO之间的映射关系,均可以通过[MapFrom],[MapTo]特性来进行配置,用法很简单,只需在前面定义的DTO类型上加上相应的特性即可:

    博客 - Blog

    博客 InputDto

    /// <summary>
    /// 输入DTO:博客信息
    /// </summary>
    [MapTo(typeof(Blog))]
    public class BlogInputDto : IInputDto<int>
    {
        //...
    }
    

    博客 OutputDto

    /// <summary>
    /// 输出DTO:博客信息
    /// </summary>
    [MapFrom(typeof(Blog))]
    public class BlogOutputDto : IOutputDto, IDataAuthEnabled
    {
        //...
    }
    

    文章 - Post

    文章 InputDto

    /// <summary>
    /// 输入DTO:文章信息
    /// </summary>
    [MapTo(typeof(Post))]
    public class PostInputDto : IInputDto<int>
    {
        //...
    }
    

    文章 OutputDto

    /// <summary>
    /// 输出DTO:文章信息
    /// </summary>
    [MapFrom(typeof(Post))]
    public class PostOutputDto : IOutputDto, IDataAuthEnabled
    {
        //...
    }
    

    至此,博客模块的数据层代码实现完毕。下面可进行数据迁移将数据结构更新到现有数据库中。

    数据迁移

    打开 VS2019 的 程序包管理器控制台,将默认项目设置为 srcLiuliu.Blogs.Web,执行如下命令添加名称为AddBlogsEntities的新的迁移记录:

    add-migration AddBlogsEntities

    再执行数据库更新命令,执行迁移

    update-database

    数据迁移完毕,整个过程输出如下:

    PM> add-migration AddBlogsEntities
    entryAssemblyName: Liuliu.Blogs.Web
    To undo this action, use Remove-Migration.
    PM> update-database
    entryAssemblyName: Liuliu.Blogs.Web
    Applying migration '20190504130412_AddBlogsEntities'.
    Done.
    PM> 
    

    打开数据库,我们将看到新的数据表dbo.Blogdbo.Post已经成功创建:

  • 相关阅读:
    MariaDB · 版本特性 · MariaDB 的 GTID 介绍
    stm8s 中断重复进入
    PCB积累
    链表的创建、增加、删除、改数据、遍历
    百度文库文字下载工具指引
    防倒灌的开关电路
    AD快速从原理图查找pcb中元件
    三目运算符填坑
    嵌入式结构化分层思想
    原码,反码,补码
  • 原文地址:https://www.cnblogs.com/guomingfeng/p/osharpns-steps-entity.html
Copyright © 2011-2022 走看看