12

五、Abp vNext 基础篇丨博客聚合功能

 3 years ago
source link: https://www.cnblogs.com/MrChuJiu/p/15192406.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

五、Abp vNext 基础篇丨博客聚合功能

系列文章列表,点击展示/隐藏

本文梯子

正文

业务篇章先从客户端开始写,另外补充一下我给项目起名的时候没多想起的太随意了,结果后面有些地方命名冲突了需要通过手动using不过问题不大。

根据第三章分层架构里面讲到的现在我们模型已经创建好了,下一步应该是去Application.Contracts层创建我们的业务接口和Dto.

Blog业务接口


    public interface IBlogAppService : IApplicationService
    {
        Task<ListResultDto<BlogDto>> GetListAsync();

        Task<BlogDto> GetByShortNameAsync(string shortName);

        Task<BlogDto> GetAsync(Guid id);
    }


    public class BlogDto : FullAuditedEntityDto<Guid>
    {
        public string Name { get; set; }

        public string ShortName { get; set; }

        public string Description { get; set; }
    }

接口写完之后,我们去Application层实现 Application.Contracts 中定义的服务接⼝,应⽤服务是⽆状态服务,实现应⽤程序⽤例。⼀个应⽤服务通常使⽤领域对象实现⽤例,获取或返回数 据传输对象DTOs,被展示层调⽤。

应⽤服务通⽤原则:

  • 实现特定⽤例的应⽤逻辑,不能在应⽤服务中实现领域逻辑(需要理清应⽤逻辑和领域逻辑⼆者的 区别)。
  • 应⽤服务⽅法不能返回实体,因为这样会打破领域层的封装性,始终只返回DTO。

大家先看下面的代码有什么问题

public class BlogAppService : CoreAppService, IBlogAppService
    {
        private readonly IRepository<Blog> _blogRepository;

        public BlogAppService(IRepository<Blog> blogRepository)
        {
            _blogRepository = blogRepository;
        }
        public async Task<ListResultDto<BlogDto>> GetListAsync()
        {
            var blogs = await _blogRepository.GetListAsync();

            return new ListResultDto<BlogDto>(
                ObjectMapper.Map<List<Blog>, List<BlogDto>>(blogs)
            );
        }

        public async Task<BlogDto> GetByShortNameAsync(string shortName)
        {
            Check.NotNullOrWhiteSpace(shortName, nameof(shortName));

            var blog =  await _blogRepository.GetAsync(x=>x.ShortName == shortName);

            if (blog == null)
            {
                throw new EntityNotFoundException(typeof(Blog), shortName);
            }

            return ObjectMapper.Map<Blog, BlogDto>(blog);
        }

        public async Task<BlogDto> GetAsync(Guid id)
        {
            var blog = await _blogRepository.GetAsync(x=>x.Id == id);

            return ObjectMapper.Map<Blog, BlogDto>(blog);
        }
    }

错误:上面代码违反了应用层原则将特定⽤例的应⽤逻辑写在了应⽤服务层。

解决上面的问题就要用到仓储,ABP默认提供的泛型仓储无法满足业务需要的时候就需要我们自定义仓储,仓储应该只针对聚合根,⽽不是所有实体。因为⼦集合实体(聚合)应该通过聚合根访问。

仓储定义写在领域层,仓储实现写在基础层,参照第三章:ABP项目分层解析关于数据库独⽴性原则的讨论

仓储的通⽤原则

  • 在领域层中定义仓储接⼝,在基础层中实现仓储接⼝(⽐如: EntityFrameworkCore 项⽬ 或 MongoDB 项⽬)
  • 仓储不包含业务逻辑,专注数据处理。
  • 仓储接⼝应该保持 数据提供程序/ORM 独⽴性。举个例⼦,仓储接⼝定义的⽅法不能返回 DbSet 对象,因为该对象由 EF Core 提供,如果使⽤ MongoDB 数据库则⽆法实现该接⼝。
  • 为聚合根创建对应仓储,⽽不是所有实体。因为⼦集合实体(聚合)应该通过聚合根访问。

项目结构

    public interface IBlogRepository : IBasicRepository<Blog, Guid>
    {
        Task<Blog> FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default);
    }



    public class EfCoreBlogRepository : EfCoreRepository<CoreDbContext, Blog, Guid>, IBlogRepository
    {
        public EfCoreBlogRepository(IDbContextProvider<CoreDbContext> dbContextProvider)
            : base(dbContextProvider)
        {

        }

        public async Task<Blog> FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default)
        {
            return await (await GetDbSetAsync()).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken));
        }
    }



    public class BlogAppService : CoreAppService, IBlogAppService
    {
        private readonly IBlogRepository _blogRepository;

        public BlogAppService(IBlogRepository blogRepository)
        {
            _blogRepository = blogRepository;
        }
        public async Task<ListResultDto<BlogDto>> GetListAsync()
        {
            var blogs = await _blogRepository.GetListAsync();

            return new ListResultDto<BlogDto>(
                ObjectMapper.Map<List<Blog>, List<BlogDto>>(blogs)
            );
        }

        public async Task<BlogDto> GetByShortNameAsync(string shortName)
        {
            Check.NotNullOrWhiteSpace(shortName, nameof(shortName));

            var blog = await _blogRepository.FindByShortNameAsync(shortName);

            if (blog == null)
            {
                throw new EntityNotFoundException(typeof(Blog), shortName);
            }

            return ObjectMapper.Map<Blog, BlogDto>(blog);
        }

        public async Task<BlogDto> GetAsync(Guid id)
        {
            var blog = await _blogRepository.GetAsync(id);

            return ObjectMapper.Map<Blog, BlogDto>(blog);
        }
    }

映射Domain对象

上面完成后我们就可以启动系统看到我们定义的接口了,但是我们还少了一步那就是映射 Domain 对象(实体和值类型)到数据库表。

CoreDbContext上下文中加入我们的实体,然后在 CoreEfCoreEntityExtensionMappings 中新建一个静态ConfigureBcvpBlogCore方法写FluentApi,这里有几个疑惑我说下,因为我目前使用的版本是4.4也就是ABP刚发布的新版本,这个版本中它移除了一些类比如ModelBuilderConfigurationOptionsDbContextModelBuilderExtensions,我就直接把ConfigureBcvpBlogCore写在CoreEfCoreEntityExtensionMappings里面了,可能后面我会在找合理的地方去单独放,另外可以看到PostTag没有出现在这里,这是因为PostTag是一个值对象作为实体的私有类型处理了,这里就能充分感受到模型建立与数据库映射抽离。


----------------------------- CoreDbContext.cs

        public DbSet<BlogCore.Blogs.Blog> Blogs { get; set; }

        public DbSet<Post> Posts { get; set; }

        public DbSet<Tag> Tags { get; set; }

        public DbSet<Comment> Comments { get; set; }


        protected override void OnModelCreating(ModelBuilder builder)
        {

            // 这里是追加不是删掉原来的
            builder.ConfigureBcvpBlogCore();

        }



----------------------------- CoreEfCoreEntityExtensionMappings.cs

 public static void ConfigureBcvpBlogCore([NotNull] this ModelBuilder builder)
        {
            Check.NotNull(builder, nameof(builder));

            if (builder.IsTenantOnlyDatabase())
            {
                return;
            }


            builder.Entity<BlogCore.Blogs.Blog>(b =>
            {
                b.ToTable(CoreConsts.DbTablePrefix + "Blogs", CoreConsts.DbSchema);

                b.ConfigureByConvention();

                b.Property(x => x.Name).IsRequired().HasMaxLength(BlogConsts.MaxNameLength).HasColumnName(nameof(BlogCore.Blogs.Blog.Name));
                b.Property(x => x.ShortName).IsRequired().HasMaxLength(BlogConsts.MaxShortNameLength).HasColumnName(nameof(BlogCore.Blogs.Blog.ShortName));
                b.Property(x => x.Description).IsRequired(false).HasMaxLength(BlogConsts.MaxDescriptionLength).HasColumnName(nameof(BlogCore.Blogs.Blog.Description));

                b.ApplyObjectExtensionMappings();
            });

            builder.Entity<Post>(b =>
            {
                b.ToTable(CoreConsts.DbTablePrefix + "Posts", CoreConsts.DbSchema);

                b.ConfigureByConvention();

                b.Property(x => x.BlogId).HasColumnName(nameof(Post.BlogId));
                b.Property(x => x.Title).IsRequired().HasMaxLength(PostConsts.MaxTitleLength).HasColumnName(nameof(Post.Title));
                b.Property(x => x.CoverImage).IsRequired().HasColumnName(nameof(Post.CoverImage));
                b.Property(x => x.Url).IsRequired().HasMaxLength(PostConsts.MaxUrlLength).HasColumnName(nameof(Post.Url));
                b.Property(x => x.Content).IsRequired(false).HasMaxLength(PostConsts.MaxContentLength).HasColumnName(nameof(Post.Content));
                b.Property(x => x.Description).IsRequired(false).HasMaxLength(PostConsts.MaxDescriptionLength).HasColumnName(nameof(Post.Description));

                b.OwnsMany(p => p.Tags, pd =>
                {
                    pd.ToTable(CoreConsts.DbTablePrefix + "PostTags", CoreConsts.DbSchema);

                    pd.Property(x => x.TagId).HasColumnName(nameof(PostTag.TagId));
                    
                });

                b.HasOne<BlogCore.Blogs.Blog>().WithMany().IsRequired().HasForeignKey(p => p.BlogId);

                b.ApplyObjectExtensionMappings();
            });

            builder.Entity<Tag>(b =>
            {
                b.ToTable(CoreConsts.DbTablePrefix + "Tags", CoreConsts.DbSchema);

                b.ConfigureByConvention();

                b.Property(x => x.Name).IsRequired().HasMaxLength(TagConsts.MaxNameLength).HasColumnName(nameof(Tag.Name));
                b.Property(x => x.Description).HasMaxLength(TagConsts.MaxDescriptionLength).HasColumnName(nameof(Tag.Description));
                b.Property(x => x.UsageCount).HasColumnName(nameof(Tag.UsageCount));

                b.ApplyObjectExtensionMappings();
            });


            builder.Entity<Comment>(b =>
            {
                b.ToTable(CoreConsts.DbTablePrefix + "Comments", CoreConsts.DbSchema);

                b.ConfigureByConvention();

                b.Property(x => x.Text).IsRequired().HasMaxLength(CommentConsts.MaxTextLength).HasColumnName(nameof(Comment.Text));
                b.Property(x => x.RepliedCommentId).HasColumnName(nameof(Comment.RepliedCommentId));
                b.Property(x => x.PostId).IsRequired().HasColumnName(nameof(Comment.PostId));

                b.HasOne<Comment>().WithMany().HasForeignKey(p => p.RepliedCommentId);
                b.HasOne<Post>().WithMany().IsRequired().HasForeignKey(p => p.PostId);

                b.ApplyObjectExtensionMappings();
            });


         

            builder.TryConfigureObjectExtensions<CoreDbContext>();

        }

接下来就是生成迁移和执行迁移了

创建项目

本节知识点:

  • 1.根据前面4章讲的知识完成博客建模
  • 2.完成业务博客业务代码
  • 3.自定义仓储

联系作者:加群:867095512 @MrChuJiu

公众号


Recommend

  • 100
    • Github github.com 6 years ago
    • Cache

    GitHub - abpframework/abp: ABP vNext

    README.md ABP

  • 17
    • www.cnblogs.com 4 years ago
    • Cache

    我和ABP vNext 的故事

    我和ABP vNext 的故事 Abp VNext是Abp的.NET Core 版本,但它不仅仅只是代码重写了...

  • 12

    2021-04-079 16 min.在 上一篇 博客中,博主和大家分享了如何在 EF Core 中...

  • 8

    ABP vNext 的实体与服务扩展技巧分享2021-04-182 20 min.使用 ABP vNext 有一个月左右啦,这中间最大的一个收获是:ABP vNext 的开发效率真的是非常好,只要你愿意取遵循它模块化、DDD...

  • 6

    ABP vNext 使用 Volo.Abp.Sms 包和 Volo.Abp.Emailing 包将短信和电子邮件作为基础设施进行了抽象,开发人员仅需要在使用的时候注入 ISmsSender 或 IEmailSender 即可实现短信发送和邮件发送。 二、源码分...

  • 5
    • www.cnblogs.com 3 years ago
    • Cache

    Abp vNext 基础篇丨分层架构

    系列文章列表,点击展示/隐藏本文梯子正文 本章节对 ABP 框架进行一个简单的介绍,摘自ABP官方,后面会在使用过程中对各个知识点进行细致的讲解。 领域驱动设计 领域驱动设计(简称...

  • 16

    Abp Vnext Vue3 的版本实现 Abp Vnext Pro 的 Vue3 实现版本 开箱即用的中后台前端/设计解决方案

  • 9

    ABP VNext框架基础知识介绍(2)--微服务的网关 ABP VNext框架如果不考虑在微服务上的应用,也就是开发单体应用解决方案,虽然也是模块化开发,但其集成使用的难度会降低一个层级,不过ABP VNext和ABP框架一样,基础内容都会设计很多内容,如数据库都支...

  • 6

    路径篇丨后疫情时代下旅游产业元宇宙(二)藏民8小时前2402根据中国国务院于2021年12月22日发布的《“十四五”旅游业发展规划》显示,未来中国旅游产业将继续坚持创新驱...

  • 8

    思变篇丨后疫情时代下旅游产业元宇宙(一)藏民8小时前2088旅游产业,一般是指凭借旅游资源和设施,专门或者主要从事招徕、接待游客、为其提供交通、游览、住宿、餐饮...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK