首页 > 数据库 >用NetCore + ReactJS 实现一个前后端分离的网站 (3) 仓储、服务层的实现以及数据库接入

用NetCore + ReactJS 实现一个前后端分离的网站 (3) 仓储、服务层的实现以及数据库接入

时间:2022-11-25 09:35:04浏览次数:43  
标签:Core Task NovelTogether NetCore 实现 ReactJS var using public

用NetCore + ReactJS 实现一个前后端分离的网站 (3) 仓储层的实现

1. 前言

这两天在仓储模式上纠结了几回,差点放弃仓储层的实现,因为从网上搜到一些文章,说efcore已经是按照仓储模式设计的了,不需要在用一个仓储层来抽象。不过,在实际使用中,拥有一些通用的方法,还是可以比较轻松地添加新功能,所以这里接着往下写。

2. 数据库接入

实体类当然是放在Model工程中,但是Context文件放哪里呢,一般小项目直接放在API层,Migration也在应用层做。
但是仓储层也要用Context,所以把Context文件也放到了Model工程中。
这个项目的数据库需要实现Code First,所以要先定义实体,并通过EFCore的migration功能自动初始化表结构,同时添加一些种子数据。

  1. 新建Models文件夹,把原本的Novel.cs移动进去并修改,设置字段属性,给EFCore生成建表语句。
Novel.cs
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace NovelTogether.Core.Model.Models
{
    public class Novel
    {
        [Key]
        public int ID { get; set; }
        [Required]
        [Column(TypeName = "nvarchar")]
        [MaxLength(128)]
        public string? Name { get; set; }
        [Required]
        [Column(TypeName = "nvarchar")]
        [MaxLength(128)]
        public string? Type { get; set; }
        [Required]
        [Column(TypeName = "nvarchar")]
        [MaxLength(128)]
        public string? SubType { get; set; }
        [Required]
        [Column(TypeName = "nvarchar")]
        [MaxLength(1024)]
        public string? Description { get; set; }
        [Required]
        public int CreatedBy { get; set; }
        [Required]
        public DateTime CreatedTime { get; set; }
        public int? ModifiedBy { get; set; }
        public DateTime? ModifiedTime { get; set; }
        [Required]
        public bool IsDeleted { get; set; }
    }
}

  1. 新建ORM文件夹,新建类NovelTogetherContext.cs
    这里有几点要注意
  • 构造函数接受一个参数,这个参数会在Program.cs通过添加服务方式设置(步骤3)。
  • 创建数据库的时候提供了一些种子数据。
NovelTogetherContext.cs
using Microsoft.EntityFrameworkCore;
using NovelTogether.Core.Model.Models;

namespace NovelTogether.Core.Model.ORM
{
    public class NovelTogetherContext: DbContext
    {
        public NovelTogetherContext(DbContextOptions<NovelTogetherContext> options):base(options)
        {
            
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // 初始化种子数据
            modelBuilder.AddNovelSeed();   
        }

        public DbSet<Novel> Novel { get; set; }
    }
}


  1. 添加种子数据,在ORM文件夹中新建ModelBuilderExtension.cs
ModelBuilderExtension.cs
using Microsoft.EntityFrameworkCore;
using NovelTogether.Core.Model.Models;

namespace NovelTogether.Core.Model.ORM
{
    public static class ModelBuilderExtension
    {
        public static void AddNovelSeed(this ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Novel>().HasData(
                new Novel { ID = 1, Name = "三国演义", Type = "古典名著", SubType = "", Description = "", CreatedBy = 1, CreatedTime = DateTime.Now, IsDeleted = false },
                new Novel { ID = 2, Name = "红楼梦", Type = "古典名著", SubType = "", Description = "", CreatedBy = 1, CreatedTime = DateTime.Now, IsDeleted = false },
                new Novel { ID = 3, Name = "水浒传", Type = "古典名著", SubType = "", Description = "", CreatedBy = 1, CreatedTime = DateTime.Now, IsDeleted = false },
                new Novel { ID = 4, Name = "西游记", Type = "古典名著", SubType = "", Description = "", CreatedBy = 1, CreatedTime = DateTime.Now, IsDeleted = false }
                );
        }
    }
}


  1. 往appsettings.json中添加配置项。
appsettings.json
"Config": {
    "Database": {
      "SqlServer": {
        "InUse": true,
        "ConnectionString": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=NovelTogether;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
      }
    }
  }

  1. 在API工程中新建Filters文件夹,创建MigrationStartupFilter.cs,提供随程序启动执行Migration的服务。
MigrationStartupFilter.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Writers;

namespace NovelTogether.Core.API.Filters
{
    public class MigrationStartupFilter<TContext> : IStartupFilter where TContext : DbContext
    {
        public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
        {
            return app =>
            {
                using (var scope = app.ApplicationServices.CreateScope())
                {
                    foreach (var context in scope.ServiceProvider.GetServices<TContext>())
                    {
                        context.Database.SetCommandTimeout(200);
                        context.Database.Migrate();
                    }
                }

                next(app);
            };
        }
    }
}


  1. 设置目标数据库的类型和连接字符串,并设置在程序启动的时候执行Migration。
    先引用EFCore相关的两个库,前者是用来连接SqlServer数据库的,后者使用管理Migration的。
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Design

然后修改入口程序。

Program.cs
// get configuration
var configuration = builder.Configuration;

#region EFCore
builder.Services.AddDbContext<NovelTogetherContext>(option =>
{
    // 因为NovelTogetherContext不在API工程中,需要手动指定Migration所在的程序集名称。
    option.UseSqlServer(configuration.GetValue<string>("Config:Database:SqlServer:ConnectionString"), b => b.MigrationsAssembly("NovelTogether.Core.API"));
});
// 在程序启动的时候执行数据库的升级
builder.Services.AddTransient<IStartupFilter, MigrationStartupFilter<NovelTogetherContext>>();
#endregion

  1. 在API工程中创建Migration
Command
dotnet ef migrations add InitialCreate
成功之后,会在API工程中创建一个文件夹Migrations。 ![image](/i/l/?n=22&i=blog/3040055/202211/3040055-20221124161514920-202418400.png)

以上步骤完毕之后,F5启动程序,就可以自动创建数据库。通过客户端工具连接数据库,可以看到初始化的四条数据。
image

下面我们接着完善仓储和服务层。

3. 仓储和服务层

  1. 在IRepository工程中新建文件夹Base,并新建接口IBaseRepository。这里面定义了基本的增删改查方法。
IBaseRepository.cs
using System.Linq.Expressions;

namespace NovelTogether.Core.IRepository.Base
{
    public interface IBaseRepository<TEntity> where TEntity : class
    {
        Task<TEntity> SelectAsync(Expression<Func<TEntity, bool>> expression);
        Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression);
        Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression, Dictionary<string, bool> sortingColumnsWithDirection);
        Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression, Dictionary<string, bool> sortingColumnsWithDirection, int pageSize, int pageIndex);

        Task<TEntity> AddAsync(TEntity entity);
        Task<List<TEntity>> AddRangeAsync(List<TEntity> entities);

        Task<TEntity> UpdateAsync(TEntity entity);
        Task<List<TEntity>> UpdateRangeAsync(List<TEntity> entities);

        Task<bool> DeleteAsync(TEntity entity);
        Task<bool> DeleteRangeAsync(List<TEntity> entities);
    }
}


  1. 在IRepository工程根目录创建接口INovelRepository,继承基接口IBaseRepository。
INovelRepository.cs
using NovelTogether.Core.Model.Models;

namespace NovelTogether.Core.IRepository.Base
{
    public interface INovelRepository: IBaseRepository<Novel>
    {
    }
}


  1. 在Repository工程中新建文件夹Base,并新建类BaseRepository,这个类继承了IBaseRepository,它里面实现了每个方法。
    注意:构造函数中的参数NovelTogetherContext是由子类传进来的。
    注意:IQueryable没有OrderBy、OrderByDescending,IOrderedQueryable没有ThenBy,ThenByDescending,需要自己扩展,代码见文章结尾。
BaseRepository.cs
using Microsoft.EntityFrameworkCore;
using NovelTogether.Core.Common.ORM;
using NovelTogether.Core.IRepository.Base;
using NovelTogether.Core.Model.ORM;
using System.Linq.Expressions;
using System.Runtime.InteropServices;

namespace NovelTogether.Core.Repository.Base
{
    // new() 用来约束TEntity必须要有一个不带参数的构造函数
    public class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : class, new()
    {
        private DbContext dbContext;

        #region 构造函数
        public BaseRepository(NovelTogetherContext novelTogetherContext)
        {
            dbContext = novelTogetherContext;
        }
        #endregion

        #region 公共方法
        public async Task<TEntity> AddAsync(TEntity entity)
        {
            await dbContext.AddAsync(entity);
            await dbContext.SaveChangesAsync();
            return entity;
        }

        public async Task<List<TEntity>> AddRangeAsync(List<TEntity> entities)
        {
            await dbContext.AddRangeAsync(entities);
            await dbContext.SaveChangesAsync();
            return entities;
        }

        public async Task<bool> DeleteAsync(TEntity entity)
        {
            dbContext.Remove(entity);
            await dbContext.SaveChangesAsync();
            return true;
        }

        public async Task<bool> DeleteRangeAsync(List<TEntity> entities)
        {
            dbContext.RemoveRange(entities);
            await dbContext.SaveChangesAsync();
            return true;
        }

        public async Task<TEntity> SelectAsync(Expression<Func<TEntity, bool>> expression)
        {
            var entity = await dbContext.Set<TEntity>().Where(expression).FirstOrDefaultAsync();
            return entity;
        }

        public async Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression)
        {
            var entities = SelectRangeQuerable(expression, new Dictionary<string, bool>());

            return await entities.ToListAsync();
        }

        public async Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression, Dictionary<string, bool> sortingColumnsWithDirection)
        {
            var entities = SelectRangeQuerable(expression, sortingColumnsWithDirection);

            return await entities.ToListAsync();
        }

        public async Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression, Dictionary<string, bool> sortingColumnsWithDirection, int pageSize, int pageIndex)
        {
            var entities = SelectRangeQuerable(expression, sortingColumnsWithDirection);
            entities = entities.Skip((pageIndex - 1) * pageSize).Take(pageSize);
            
            return await entities.ToListAsync();
        }

        public async Task<TEntity> UpdateAsync(TEntity entity)
        {
            dbContext.Update(entity);
            await dbContext.SaveChangesAsync();
            return entity;
        }

        public async Task<List<TEntity>> UpdateRangeAsync(List<TEntity> entities)
        {
            dbContext.UpdateRange(entities);
            await dbContext.SaveChangesAsync();
            return entities;
        }
        #endregion

        #region 私有方法
        private IQueryable<TEntity> SelectRangeQuerable(Expression<Func<TEntity, bool>> expression, Dictionary<string, bool> sortingColumnsWithDirection)
        {
            var entities = dbContext.Set<TEntity>().Where(expression);

            if (sortingColumnsWithDirection.Keys.Count > 0)
            {
                var first = sortingColumnsWithDirection.First();

                IOrderedQueryable<TEntity> orderedEntities;
                if (first.Value)
                {
                    orderedEntities = entities.OrderBy(first.Key);
                }
                else
                {
                    orderedEntities = entities.OrderByDescending(first.Key);
                }

                // remove first
                sortingColumnsWithDirection.Remove(first.Key);

                // sort left columns
                foreach (KeyValuePair<string, bool> kvp in sortingColumnsWithDirection)
                {
                    if (kvp.Value)
                    {
                        orderedEntities = orderedEntities.ThenBy(kvp.Key);
                    }
                    else
                    {
                        orderedEntities = orderedEntities.ThenByDescending(kvp.Key);
                    }
                }

                return orderedEntities;
            }

            return entities;
        }
        #endregion
    }
}


  1. 在Repository工程的根目录新建类NovelRepository,继承BaseRepository和INovelRepository,并把注入的NovelTogetherContext传给父类的构造函数。
NovelRepository.cs
using NovelTogether.Core.IRepository.Base;
using NovelTogether.Core.Model.Models;
using NovelTogether.Core.Model.ORM;
using NovelTogether.Core.Repository.Base;

namespace NovelTogether.Core.Repository
{
    public class NovelRepository: BaseRepository<Novel>, INovelRepository
    {
        public NovelRepository(NovelTogetherContext novelTogetherContext):base(novelTogetherContext)
        {
            
        }
    }
}


  1. 在IService工程中新建文件Base,并新建接口IBaseService。这里面同样定义了基本的增删改查方法,和IBaseRepository中的一样,当然还可以加上一些别的方法。
IBaseService.cs
using System.Linq.Expressions;

namespace NovelTogether.Core.IService.Base
{
    public interface IBaseService<TEntity> where TEntity : class
    {
        Task<TEntity> SelectAsync(Expression<Func<TEntity, bool>> expression);
        Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression);
        Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression, Dictionary<string, bool> sortingColumnsWithDirection);
        Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression, Dictionary<string, bool> sortingColumnsWithDirection, int pageSize, int pageIndex);

        Task<TEntity> AddAsync(TEntity entity);
        Task<List<TEntity>> AddRangeAsync(List<TEntity> entities);

        Task<TEntity> UpdateAsync(TEntity entity);
        Task<List<TEntity>> UpdateRangeAsync(List<TEntity> entities);

        Task<bool> DeleteAsync(TEntity entity);
        Task<bool> DeleteRangeAsync(List<TEntity> entities);
    }
}


  1. 在IService工程的根目录新建接口INovelService,继承基接口IBaseService。
INovelService.cs
using NovelTogether.Core.IService.Base;
using NovelTogether.Core.Model.Models;

namespace NovelTogether.Core.IService
{
    public interface INovelService: IBaseService<Novel>
    {
    }
}

  1. 在Service工程中新建文件夹Base,并新建类BaseService,继承基接口IBaseService,子类由依赖注入得到的IBaseRepository通过构造函数传递给BaseService。
BaseService.cs
using NovelTogether.Core.IRepository.Base;
using NovelTogether.Core.IService.Base;
using System.Linq.Expressions;

namespace NovelTogether.Core.Service.Base
{
    public class BaseService<TEntity> : IBaseService<TEntity> where TEntity : class, new()
    {
        private IBaseRepository<TEntity> repository;

        public BaseService(IBaseRepository<TEntity> Repository)
        {
            this.repository = Repository;
        }

        public async Task<TEntity> AddAsync(TEntity entity)
        {
            return await repository.AddAsync(entity);
        }

        public async Task<List<TEntity>> AddRangeAsync(List<TEntity> entities)
        {
            return await repository.AddRangeAsync(entities);
        }

        public async Task<bool> DeleteAsync(TEntity entity)
        {
            return await repository.DeleteAsync(entity);
        }

        public async Task<bool> DeleteRangeAsync(List<TEntity> entities)
        {
            return await repository.DeleteRangeAsync(entities);
        }

        public async Task<TEntity> SelectAsync(Expression<Func<TEntity, bool>> expression)
        {
            return await repository.SelectAsync(expression);
        }

        public async Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression)
        {
            return await repository.SelectRangeAsync(expression);
        }

        public async Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression, Dictionary<string, bool> sortingColumnsWithDirection)
        {
            return await repository.SelectRangeAsync(expression, sortingColumnsWithDirection);
        }

        public async Task<List<TEntity>> SelectRangeAsync(Expression<Func<TEntity, bool>> expression, Dictionary<string, bool> sortingColumnsWithDirection, int pageSize, int pageIndex)
        {
            return await repository.SelectRangeAsync(expression, sortingColumnsWithDirection, pageSize, pageIndex);
        }

        public async Task<TEntity> UpdateAsync(TEntity entity)
        {
            return await repository.UpdateAsync(entity);
        }

        public async Task<List<TEntity>> UpdateRangeAsync(List<TEntity> entities)
        {
            return await repository.UpdateRangeAsync(entities);
        }
    }
}


  1. 在Service工程的根目录新建类NovelService,继承BaseService。
NovelService.cs
using NovelTogether.Core.IRepository.Base;
using NovelTogether.Core.IService;
using NovelTogether.Core.Model.Models;
using NovelTogether.Core.Service.Base;

namespace NovelTogether.Core.Service
{
    public class NovelService : BaseService<Novel>, INovelService
    {
        public NovelService(INovelRepository repository):base(repository)
        {

        }
    }
}

  1. 修改NovelController.cs
NovelController.cs
using Microsoft.AspNetCore.Mvc;
using NovelTogether.Core.IService;
using NovelTogether.Core.Model.Models;

namespace NovelTogether.Core.API.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class NovelController : Controller
    {
        private readonly INovelService _novelService;

        // 通过构造函数注入依赖
        public NovelController(INovelService novelService)
        {
            _novelService = novelService;
        }

        [HttpGet]
        public async Task<Novel> Get(int id)
        {   
            return await _novelService.SelectAsync(x => x.ID == id);
        }
    }
}

  1. F5执行

4. 扩展方法

EntityFrameworkExtensions.cs
using System.Linq.Expressions;
using System.Reflection;

namespace NovelTogether.Core.Common.ORM
{
    public static class EntityFrameworkExtensions
    {
        public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> query, string propertyName)
        {
            var entityType = typeof(TSource);

            //Create x=>x.PropName
            var propertyInfo = entityType.GetProperty(propertyName);
            ParameterExpression arg = Expression.Parameter(entityType, "x");
            MemberExpression property = Expression.Property(arg, propertyName);
            var selector = Expression.Lambda(property, new ParameterExpression[] { arg });

            //Get System.Linq.Queryable.OrderBy() method.
            var enumarableType = typeof(System.Linq.Queryable);
            var method = enumarableType.GetMethods()
                 .Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
                 .Where(m =>
                 {
                     var parameters = m.GetParameters().ToList();
                     return parameters.Count == 2;
                 }).Single();

            //The linq's OrderBy<TSource, TKey> has two generic types, which provided here
            MethodInfo genericMethod = method.MakeGenericMethod(entityType, propertyInfo.PropertyType);

            // Call query.OrderBy(selector), with query and selector: x=> x.PropName
            // Note that we pass the selector as Expression to the method and we don't compile it.
            // By doing so EF can extract "order by" columns and generate SQL for it
            var newQuery = (IOrderedQueryable<TSource>)genericMethod.Invoke(genericMethod, new object[] { query, selector });
            return newQuery;
        }

        public static IOrderedQueryable<TSource> ThenBy<TSource>(this IOrderedQueryable<TSource> query, string propertyName)
        {
            var entityType = typeof(TSource);

            //Create x=>x.PropName
            var propertyInfo = entityType.GetProperty(propertyName);
            ParameterExpression arg = Expression.Parameter(entityType, "x");
            MemberExpression property = Expression.Property(arg, propertyName);
            var selector = Expression.Lambda(property, new ParameterExpression[] { arg });

            //Get System.Linq.Queryable.OrderBy() method.
            var enumarableType = typeof(System.Linq.Queryable);
            var method = enumarableType.GetMethods()
                 .Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
                 .Where(m =>
                 {
                     var parameters = m.GetParameters().ToList();
                     return parameters.Count == 2;
                 }).Single();

            //The linq's OrderBy<TSource, TKey> has two generic types, which provided here
            MethodInfo genericMethod = method.MakeGenericMethod(entityType, propertyInfo.PropertyType);

            // Call query.OrderBy(selector), with query and selector: x=> x.PropName
            // Note that we pass the selector as Expression to the method and we don't compile it.
            // By doing so EF can extract "order by" columns and generate SQL for it
            var newQuery = (IOrderedQueryable<TSource>)genericMethod.Invoke(genericMethod, new object[] { query, selector });
            return newQuery;
        }

        public static IOrderedQueryable<TSource> OrderByDescending<TSource>(this IQueryable<TSource> query, string propertyName)
        {
            var entityType = typeof(TSource);

            //Create x=>x.PropName
            var propertyInfo = entityType.GetProperty(propertyName);
            ParameterExpression arg = Expression.Parameter(entityType, "x");
            MemberExpression property = Expression.Property(arg, propertyName);
            var selector = Expression.Lambda(property, new ParameterExpression[] { arg });

            //Get System.Linq.Queryable.OrderBy() method.
            var enumarableType = typeof(System.Linq.Queryable);
            var method = enumarableType.GetMethods()
                 .Where(m => m.Name == "OrderByDescending" && m.IsGenericMethodDefinition)
                 .Where(m =>
                 {
                     var parameters = m.GetParameters().ToList();
                     return parameters.Count == 2;
                 }).Single();

            //The linq's OrderByDescending<TSource, TKey> has two generic types, which provided here
            MethodInfo genericMethod = method.MakeGenericMethod(entityType, propertyInfo.PropertyType);

            // Call query.OrderByDescending(selector), with query and selector: x=> x.PropName
            // Note that we pass the selector as Expression to the method and we don't compile it.
            // By doing so EF can extract "order by" columns and generate SQL for it
            var newQuery = (IOrderedQueryable<TSource>)genericMethod.Invoke(genericMethod, new object[] { query, selector });
            return newQuery;
        }

        public static IOrderedQueryable<TSource> ThenByDescending<TSource>(this IOrderedQueryable<TSource> query, string propertyName)
        {
            var entityType = typeof(TSource);

            //Create x=>x.PropName
            var propertyInfo = entityType.GetProperty(propertyName);
            ParameterExpression arg = Expression.Parameter(entityType, "x");
            MemberExpression property = Expression.Property(arg, propertyName);
            var selector = Expression.Lambda(property, new ParameterExpression[] { arg });

            //Get System.Linq.Queryable.OrderBy() method.
            var enumarableType = typeof(System.Linq.Queryable);
            var method = enumarableType.GetMethods()
                 .Where(m => m.Name == "OrderByDescending" && m.IsGenericMethodDefinition)
                 .Where(m =>
                 {
                     var parameters = m.GetParameters().ToList();
                     return parameters.Count == 2;
                 }).Single();

            //The linq's OrderByDescending<TSource, TKey> has two generic types, which provided here
            MethodInfo genericMethod = method.MakeGenericMethod(entityType, propertyInfo.PropertyType);

            // Call query.OrderByDescending(selector), with query and selector: x=> x.PropName
            // Note that we pass the selector as Expression to the method and we don't compile it.
            // By doing so EF can extract "order by" columns and generate SQL for it
            var newQuery = (IOrderedQueryable<TSource>)genericMethod.Invoke(genericMethod, new object[] { query, selector });
            return newQuery;
        }
    }
}


5.总结

这一节做了以下事情:

  1. 通过EFCore引入了数据库,实现Code First,并注册为服务。
  2. 创建仓储层和服务层的基接口和基类,具体的仓储类和服务类只需要简单继承基类就可以向上层提供服务。
  • 数据访问层在BaseRepository中实现,这个类中的Context由子类通过构造函数注入。
  • 服务层在BaseService中实现,子类把注入的Repository通过构造函数传给BaseService,然后调用仓储层的方法。

下一节引入用户注册、登录。会有两种实现,一种是在本地数据库中管理用户,另一种是通过第三方的授权来访问网站。

标签:Core,Task,NovelTogether,NetCore,实现,ReactJS,var,using,public
From: https://www.cnblogs.com/wang-yi-yi/p/16911366.html

相关文章