ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Ultimate ASP.NET CORE 6.0 Web API --- 读书笔记(3)

2022-06-16 19:35:02  阅读:157  来源: 互联网

标签:CORE ASP repository Repository 读书笔记 void new RepositoryContext public


Onion Architecture Implementation

本文内容来自书籍: Marinko Spasojevic - Ultimate ASP.NET Core Web API - From Zero To Six-Figure Backend Developer (2nd edition)

这章是说的洋葱架构实现,学习如何创建不同的层,以此分割应用的不同部分。

对数据服务,我们需要创建Model,使用code first的方法将Model转换成数据库表

我们将会创建一个Repository层作为数据访问层,当创建这样一个抽象层后,我们会把业务逻辑和数据访问分割开。

这样我们的业务逻辑代码会变得更加干净,然后将所有的业务逻辑划分在Service层,将外部的访问划分在presentation

关于洋葱的切分有不同的方法,这里我们将它分割为四个层次

  • Domain Layer
  • Service Layer
  • Infrastructure Layer
  • Presentation Layer

然后PresentationInfrastructure在层次结构上是在同一个level

下面看看更多的细节,看看为什么要这样做

3.1.1 Advantages of the Onion Architecture

所有层次间的交互时严格按照接口的定义

依赖关系的流向时指向洋葱的中心的

在整个项目中使用依赖倒置,依赖抽象而不是实现,可以允许我们在运行时切换实现

在编译时依赖抽象,在运行时提供实现

当所有东西都是依赖抽象的,这样会使得整个架构变得容易测试

因为我们可以通过Moq来替换抽象的实现,这样我们在编写业务逻辑的时候,就不需要依赖任何实现的细节

如果需要任何来自外部的服务,只需要创建一个接口,然后consume,不需要担心它的实现

3.1.2 Flow of Dependencies

在洋葱里面,层次越深,所需要的依赖越少

Domain layer没有任何直接的依赖外部的层次,它是个孤岛一样的

洋葱的低层次定义接口,高层次的洋葱实现这些接口

使用这种方法,我们就可以将所有的业务逻辑封装在ServiceDomain中,而不需要知道任何实现的细节

Service中,我们只知道定义在Domain中的接口

3.2 Creating Models

根据之前的理论,创建一个库Entities,然后在里面创建一个文件夹Models,它是包含了所有的模型

Entities代表了EF Core这个框架用来映射到数据库的所有模型

然后再创建一个新的库Repository,它是数据库的上下文和repository的实现

Repository是关于数据库访问的实现,它需要引用EntityFrameworkCoreEntities

然后需要创建一个RepositoryContext继承EntityFrameworkCoreDbContext,用来代表访问数据库的上下文

在主项目中,需要配置数据库连接的信息,在appsettings.json中,而且主项目需要引用Repository

 "ConnectionStrings": {
 "sqlConnection": "server=.; database=CompanyEmployee; Integrated Security=true"
 }

然后在Repository项目中,需要创建关于数据库上下文构建的工厂类

public class RepositoryContextFactory : IDesignTimeDbContextFactory<RepositoryContext>
{
    public RepositoryContext CreateDbContext(string[] args)
    {
        var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();
        var builder = new DbContextOptionsBuilder<RepositoryContext>()
            .UseSqlServer(configuration.GetConnectionString("sqlConnection"));
        return new RepositoryContext(builder.Options);
    } }

3.4 Migration and Initial Data Seed

迁移是标准的创建或更新数据库的过程,当我们创建Model之后,我们就可以将Model映射到真实的数据库中

首先修改之前的创建数据库上下文的方法

public class RepositoryContextFactory : IDesignTimeDbContextFactory<RepositoryContext>
{
    public RepositoryContext CreateDbContext(string[] args)
    {
        var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();
        var builder = new DbContextOptionsBuilder<RepositoryContext>()
            .UseSqlServer(configuration.GetConnectionString("sqlConnection"),
                b => b.MigrationsAssembly("CompanyEmployees"));
        return new RepositoryContext(builder.Options);
    } }

修改这个是因为,我们的迁移程序集不在主程序,而是在Repository

在迁移之前,需要引用这个库Microsoft.EntityFrameworkCore.Tools,然后执行迁移并更新数据库

在创建数据库之后,我们一些初始数据,在Repository中创建一个文件夹Configuration

创建一个类CompanyConfiguration用以初始化

public class CompanyConfiguration : IEntityTypeConfiguration<Company>
{
    public void Configure(EntityTypeBuilder<Company> builder)
    {
        builder.HasData
        (
            new Company
            {
                Id = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870"),
                Name = "IT_Solutions Ltd",
                Address = "583 Wall Dr. Gwynn Oak, MD 21207",
                Country = "USA"
            },
            new Company
            {
                Id = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3"),
                Name = "Admin_Solutions Ltd",
                Address = "312 Forest Avenue, BF 923",
                Country = "USA"
            }
        );
    } 
}

然后修改RepositoryContext

public class RepositoryContext: DbContext
{
    public RepositoryContext(DbContextOptions options)
    : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new CompanyConfiguration());
        modelBuilder.ApplyConfiguration(new EmployeeConfiguration());
    }

    public DbSet<Company> Companies { get; set; }
    public DbSet<Employee> Employees { get; set; }
}

然后再迁移和更新数据库

3.5 Repository Pattern Logic

我们需要创建通用的repository,用以提供CRUD方法

然后对repository做一个包装,用作服务注册到IoC中

最后,我们将实例化这个repository,并且在任何controllers中将需要的repository注入

  1. 首先,在Contracts项目中,为repository创建接口
public interface IRepositoryBase<T> {
    IQueryable<T> FindAll(bool trackChanges);
    IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression, 
    bool trackChanges);
    void Create(T entity);
    void Update(T entity);
    void Delete(T entity);
}
  1. Repository引用Contracts,然后创建一个抽象类RepositoryBase用作对IRepositoryBase的实现
public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
{
    protected RepositoryContext RepositoryContext;
    public RepositoryBase(RepositoryContext repositoryContext) 
    => RepositoryContext = repositoryContext;
    
    public IQueryable<T> FindAll(bool trackChanges) =>
        !trackChanges 
            ? RepositoryContext.Set<T>().AsNoTracking() 
            : RepositoryContext.Set<T>();

    public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression,
    bool trackChanges) =>
        !trackChanges 
            ? RepositoryContext.Set<T>()
                .Where(expression)
                .AsNoTracking() 
            : RepositoryContext.Set<T>()
                .Where(expression); 

    public void Create(T entity) => RepositoryContext.Set<T>().Add(entity);
    public void Update(T entity) => RepositoryContext.Set<T>().Update(entity);
    public void Delete(T entity) => RepositoryContext.Set<T>().Remove(entity);
}

3.6 Repository User Interfaces and Classes

现在创建实际使用的类,它是继承自RepositoryBase,而且,它需要有特定于模型的接口,这个接口可能有其他特殊的方法

这样,我们就可以将公共的逻辑和特定于模型的逻辑切割分开

  1. 首先在Contracts中建立接口
namespace Contracts
{
    public interface ICompanyRepository
    {
    } 
}

namespace Contracts
{
    public interface IEmployeeRepository
    {
    } 
}
  1. 然后在Repository中,创建User类
public class CompanyRepository : RepositoryBase<Company>, ICompanyRepository
{
    public CompanyRepository(RepositoryContext repositoryContext) 
    : base(repositoryContext)
    {
    } 
}

3.7 Creating a Repository Manager

从API中获取由多个repository组成的结果,是非常正常的逻辑,所以有时候需要实例化多个repository来从数据库中拉取数据

如果只有两个或者很少的repository,当然没有问题,但是如果有非常多的不同repository的时候,就会变得非常复杂

所以我们需要构建一个Repository Manager,用它来帮我们实例化repository,并且注册到IoC中

  1. 首先,在Contract中创建接口
public interface IRepositoryManager
{
    ICompanyRepository Company { get; }
    IEmployeeRepository Employee { get; }
    void Save();
}
  1. 然后在Repository中,实现这个接口
public sealed class RepositoryManager : IRepositoryManager
{
    private readonly RepositoryContext _repositoryContext;
    private readonly Lazy<ICompanyRepository> _companyRepository;
    private readonly Lazy<IEmployeeRepository> _employeeRepository;

    public RepositoryManager(RepositoryContext repositoryContext)
    {
        _repositoryContext = repositoryContext;
        _companyRepository = new Lazy<ICompanyRepository>(() => new
        CompanyRepository(repositoryContext));
        _employeeRepository = new Lazy<IEmployeeRepository>(() => new
        EmployeeRepository(repositoryContext));
    }

    public ICompanyRepository Company => _companyRepository.Value;

    public IEmployeeRepository Employee => _employeeRepository.Value;

    public void Save() => _repositoryContext.SaveChanges();
}
  1. 最后需要将这个RepositoryManager注册到主项目中
public static void ConfigureRepositoryManager(this IServiceCollection services) =>
 services.AddScoped<IRepositoryManager, RepositoryManager>();

// Program.cs
builder.Services.ConfigureRepositoryManager();

3.8 Adding a Service Layer

Service是在DomainContractsDomain的一部分)的上一层,

所以Service会引用Domain

Service Layer会切分为两个项目Service.ContractsService

Service.Contracts是用于定义服务接口,以此封装主要的业务逻辑

然后会有三个接口

  • ICompanyService
  • IEmployeeService
  • IServiceManager

其实这几个接口和Repository中使用的模式是一样的,接下来就是在Service中实现这几个接口

internal sealed class CompanyService : ICompanyService
{
    private readonly IRepositoryManager _repository;
    private readonly ILoggerManager _logger;

    public CompanyService(IRepositoryManager repository, ILoggerManager logger)
    {
        _repository = repository;
        _logger = logger;
    } 
}
public sealed class ServiceManager : IServiceManager
{
    private readonly Lazy<ICompanyService> _companyService;
    private readonly Lazy<IEmployeeService> _employeeService;
    public ServiceManager(IRepositoryManager repositoryManager, ILoggerManager logger)
    {
        _companyService = new Lazy<ICompanyService>(() => new 
            CompanyService(repositoryManager, logger));

        _employeeService = new Lazy<IEmployeeService>(() => new
            EmployeeService(repositoryManager, logger));
    }

    public ICompanyService CompanyService => _companyService.Value;
    public IEmployeeService EmployeeService => _employeeService.Value;
}

然后在主项目中引用Service,并注册服务

public static void ConfigureServiceManager(this IServiceCollection services) =>
    services.AddScoped<IServiceManager, ServiceManager>();

// Program.cs
builder.Services.ConfigureServiceManager();

3.9 Registering RepositoryContext at a Runtime

在实现了IDesignTimeDbContextFactory接口的RepositoryContextFactory中,我们可以在设计时就注册了RepositoryContext,这可以让我们在迁移的时候,在别的项目找到RepositoryContext并执行

然后我们需要修改数据库的注册方法

public static void ConfigureSqlContext(this IServiceCollection services, 
IConfiguration configuration) =>
    services.AddDbContext<RepositoryContext>(opts =>
        opts.UseSqlServer(configuration.GetConnectionString("sqlConnection")));

不需要MigrationAssembly这个方法了

项目到了后面,在迁移数据库的时候,还是出现了错误提示,还是需要添加MigrationAssembly这个方法来指定

标签:CORE,ASP,repository,Repository,读书笔记,void,new,RepositoryContext,public
来源: https://www.cnblogs.com/huangwenhao1024/p/16383167.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有