Chapter3:DATABASE MODEL AND REPOSITORY PATTERN

3 DATABASE MODEL AND REPOSITORY PATTERN

In this chapter, we are going to create a database model and transfer it to the MSSQL database by using the code first approach. So, we are going to learn how to create entities (model classes), how to work with the DbContext class, and how to use migrations to transfer our created database model to the real database. Of course, it is not enough to just create a database model and transfer it to the database. We need to use it as well, and for that, we will create a Repository pattern as a data access layer.
在本章中,我们将创建一个数据库模型,并使用代码优先方法将其传输到 MSSQL 数据库。因此,我们将学习如何创建实体(模型类),如何使用DbContext类,以及如何使用迁移将我们创建的数据库模型传输到真实数据库。当然,仅仅创建一个数据库模型并将其传输到数据库是不够的。我们还需要使用它,为此,我们将创建一个存储库模式作为数据访问层。‌‌

With the Repository pattern, we create an abstraction layer between the data access and the business logic layer of an application. By using it, we are promoting a more loosely coupled approach to access our data in the database.
使用存储库模式,我们在应用程序的数据访问和业务逻辑层之间创建了一个抽象层。通过使用它,我们正在推广一种更松散耦合的方法来访问数据库中的数据。

Also, our code becomes cleaner, easier to maintain, and reusable. Data access logic is stored in a separate class, or sets of classes called a repository, with the responsibility of persisting the application’s business model.
此外,我们的代码变得更加清晰,更易于维护和可重用。数据访问逻辑存储在一个单独的类或称为存储库的类集中,负责持久化应用程序的业务模型。

So, let’s start with the model classes first.
因此,让我们先从模型类开始。

3.1 Creating Models

Using the example from the second chapter of this book, we are going to extract a new Class Library (.NET Core) project named Entities.
使用本书第二章中的示例,我们将提取一个名为 Entities 的新类库 (.NET Core) 项目。(新建一个Entities类库项目)

Don’t forget to add the reference from the main project to the Entities project.
不要忘记将主项目中的引用添加到实体项目中。

Inside it, we are going to create a folder named Models, which will contain all the model classes (entities). Entities represent classes that Entity Framework Core uses to map our database model with the tables from the database. The properties from entity classes will be mapped to the database columns.
在其中,我们将创建一个名为 Models 的文件夹,该文件夹将包含所有模型类(实体)。实体表示实体框架核心用于将数据库模型与表映射的类从数据库中。实体类的属性将映射到数据库列。

So, in the Models folder we are going to create two classes and modify them:
因此,在 Models 文件夹中,我们将创建两个类并对其进行修改:

//Company.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;

namespace Entities.Models
{
    public class Company { 
        [Column("CompanyId")] 
        public Guid Id { get; set; } 
        [Required(ErrorMessage = "Company name is a required field.")]
        [MaxLength(60, ErrorMessage = "Maximum length for the Name is 60 characters.")] 
        public string Name { get; set; } 
        
        [Required(ErrorMessage = "Company address is a required field.")]
        [MaxLength(60, ErrorMessage = "Maximum length for the Address is 60 characters")] 
        public string Address { get; set; } 
        
        public string Country { get; set; } 
        public ICollection<Employee> Employees { get; set; } }
    public class Employee { 
        [Column("EmployeeId")] 
        public Guid Id { get; set; } 

        [Required(ErrorMessage = "Employee name is a required field.")]
        [MaxLength(30, ErrorMessage = "Maximum length for the Name is 30 characters.")] 
        public string Name { get; set; } 

        [Required(ErrorMessage = "Age is a required field.")] 
        public int Age { get; set; }

        [Required(ErrorMessage = "Position is a required field.")]
        [MaxLength(20, ErrorMessage = "Maximum length for the Position is 20 characters.")] 
        public string Position { get; set; } 

        [ForeignKey(nameof(Company))] 
        public Guid CompanyId { get; set; } 

        public Company Company { get; set; } }
}

We have created two classes: the Company and Employee. Those classes contain the properties which Entity Framework Core is going to map to the columns in our tables in the database. But not all the properties will be mapped as columns. The last property of the Company class (Employees) and the last property of the Employee class (Company) are navigational properties; these properties serve the purpose of defining the relationship between our models.
我们创建了两个类:公司和员工。这些类包含实体框架核心将映射到数据库中表中的列的属性。但并非所有属性都将映射为列。公司类(员工)的最后一个属性和员工类(公司)的最后一个属性是导航属性;这些属性用于定义模型之间的关系。

We can see several attributes in our entities. The [Column] attribute will specify that the Id property is going to be mapped with a different name in the database. The [Required] and [MaxLength] properties are here for validation purposes. The first one declares the property as mandatory and the second one defines its maximum length.
我们可以在实体中看到几个属性。[Column] 特性将指定 Id 属性将在数据库中使用不同的名称进行映射。此处的 [Required][MaxLength] 属性用于验证目的。第一个将属性声明为必需属性,第二个定义其最大长度。

Once we transfer our database model to the real database, we are going to see how all these validation attributes and navigational properties affect the column definitions.
将数据库模型传输到实际数据库后,我们将看到所有这些验证特性和导航属性如何影响列定义。

3.2 Context Class and The Database Connection

Now, let’s create the context class, which will be a middleware component for communication with the database. It must inherit from the Entity Framework Core’s DbContext class and it consists of DbSet properties, which EF Core is going to use for the communication with the database. Because we are working with the DBContext class, we need to install the Microsoft.EntityFrameworkCore package in the Entities project.
现在,让我们创建上下文类,它将是用于与数据库通信的中间件组件。它必须继承自实体框架核心的 DbContext 类,并且它由 DbSet 属性组成,EF Core 将使用这些属性与数据库进行通信。由于我们使用的是 DBContext 类,因此需要安装Entities 项目中的 Microsoft.EntityFrameworkCore 包。

So, let’s navigate to the root of the Entities project and create the RepositoryContext class:
因此,让我们导航到Entities项目的根并创建RepositoryContext类:

//RepositoryContext.cs
using Entities.Models;
using Microsoft.EntityFrameworkCore;
namespace Entities
{
    public class RepositoryContext : DbContext
    {
        public RepositoryContext(DbContextOptions options) : base(options) { }
        public DbSet<Company> Companies { get; set; }
        public DbSet<Employee> Employees { get; set; }
    }
}

After the class modification, let’s open the appsettings.json file and add the connection string named sqlconnection:
修改类后,让我们打开 appsettings.json 文件并添加名为 sqlconnection 的连接字符串:

//appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "ConnectionStrings": {
    "sqlConnection": "server=.; database=CompanyEmployee; Integrated Security=true"
  },
  "AllowedHosts": "*"
  }

It is quite important to have the JSON object with the ConnectionStrings name in our appsettings.json file, and soon you will see why.
在我们的 appsettings.json 文件中具有 ConnectionStrings 名称的 JSON 对象非常重要,很快你就会明白为什么。

We have one more step to finish the database model configuration. We need to register the RepositoryContext class in the application’s dependency injection container as we did with the LoggerManager class in the previous chapter.
我们还有一个步骤来完成数据库模型配置。我们需要在应用程序的依赖关系注入容器中注册 RepositoryContext 类,就像我们在上一章中对 LoggerManager 类所做的那样。

So, let’s open the ServiceExtensions class and add the additional method:
因此,让我们打开 ServiceExtensions 类并添加其他方法:

//ServiceExtensions.cs
using Contracts;
using Entities;
using LoggerService;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
//using Microsoft.EntityFrameworkCore.SqlServer;
using Microsoft.EntityFrameworkCore;

namespace CompanyEmployees.Extensions
{
    public static class ServiceExtensions
    {

        public static void ConfigureCors(this IServiceCollection services) =>
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy", builder =>
                builder.AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());
            });

        public static void ConfigureIISIntegration(this IServiceCollection service) =>
            service.Configure<IISOptions>(options => { });



        public static void ConfigureLoggerService(this IServiceCollection services) => 
            services.AddScoped<ILoggerManager, LoggerManager>();

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


    }
}

With the help of the IConfiguration configuration parameter, we can use the GetConnectionString method to access the connection string from the appsettings.json file. Moreover, to be able to use the UseSqlServer method, we need to install the Microsoft.EntityFrameworkCore.SqlServer package. If we navigate to the GetConnectionString method definition, we will see that it is an extension method that uses the ConnectionStrings name from the appsettings.json file to fetch the connection string by the provided key:
IConfiguration configuration配置参数的帮助下,我们可以使用 GetConnectionString 方法从 appsettings.json 文件中访问连接字符串。此外,为了能够使用 UseSqlServer 方法,我们需要安装 Microsoft.EntityFrameworkCore.SqlServer 包。如果我们导航到 GetConnectionString 方法定义,我们将看到它是一个扩展方法,它使用 appsettings.json 文件中的 ConnectionStrings 名称通过提供的键获取连接字符串:

alt

Afterward, in the Startup class in the ConfigureServices method, we are going to add the context service to the IOC right above the services.AddControllers() line:
之后,在 ConfigureServices 方法的 Startup 类中,我们将上下文服务添加到 services.AddControllers() 正上方的行:

//Startup.cs
public void ConfigureServices(IServiceCollection services)
        {
            services.ConfigureCors();
            services.ConfigureIISIntegration();
            services.ConfigureLoggerService();
            services.ConfigureSqlContext(Configuration);

            services.AddControllers();
        }

3.3 Migration and Initial Data Seed

Migration is a standard process of creating and updating the database from our application. Since we are finished with the database model creation, we can transfer that model to the real database. But we need to modify our ConfigureSqlContext method first:
迁移是从我们的应用程序创建和更新数据库的标准过程。由于我们已完成数据库模型的创建,因此我们可以将该模型传输到实际数据库。但是我们需要先修改我们的 ConfigureSqlContext 方法:

//ServiceExtensions.cs
using Contracts;
using Entities;
using LoggerService;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;

namespace CompanyEmployees.Extensions
{
    public static class ServiceExtensions
    {

        public static void ConfigureCors(this IServiceCollection services) =>
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy", builder =>
                builder.AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());
            });

        public static void ConfigureIISIntegration(this IServiceCollection service) =>
            service.Configure<IISOptions>(options => { });

        public static void ConfigureLoggerService(this IServiceCollection services) => 
            services.AddScoped<ILoggerManager, LoggerManager>();

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

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

We have to make this change because migration assembly is not in our main project, but in the Entities project. So, we just change the project for the migration assembly.
我们必须进行此更改,因为迁移程序集不在我们的主项目中,而是在 Entities 项目中。因此,我们只需更改迁移程序集的项目。

Before we execute our migration commands, we have to install an additional ef core library: Microsoft.EntityFrameworkCore.Tools
在执行迁移命令之前,我们必须安装一个额外的 ef 核心库:Microsoft.EntityFrameworkCore.Tools

Now, let’s open the Package Manager Console window and create our first migration: PM> Add-Migration DatabaseCreation
现在,让我们打开程序包管理器控制台(Package Manager Console)窗口并创建我们的第一个迁移:

PM> Add-Migration DatabaseCreation

每个包都由其所有者许可给你。NuGet 不负责第三方包,也不授予其许可证。一些包可能包括受其他许可证约束的依赖关系。单击包源(源) URL 可确定任何依赖关系。
程序包管理器控制台主机版本 6.3.0.131
键入 "get-help NuGet" 可查看所有可用的 NuGet 命令。
PM> Add-Migration DatabaseCreation
Build started...
Build succeeded.
To undo this action, use Remove-Migration.

With this command, we are creating migration files and we can find them in the Migrations folder in our main project:
使用此命令,我们正在创建迁移文件,并且可以在主项目的“ Migrations ”文件夹中找到它们:

alt

With those files in place, we can apply migration: PM> Update-Database
有了这些文件,我们就可以应用迁移:PM> Update-Database

Excellent. We can inspect our database now:
非常好。我们现在可以检查我们的数据库:

alt

Once we have the database and tables created, we should populate them with some initial data. To do that, we are going to create another folder called Configuration in the Entities project and add the CompanyConfiguration class:
创建数据库和表后,我们应该用一些初始数据填充它们。为此,我们将在 Entities 项目中创建另一个名为“Configuration”的文件夹,并添加 CompanyConfiguration 类:

using Entities.Models;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore;
using System;

namespace Entities.Configuration
{
    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" }); 
                }
    }
}

Let’s do the same thing for the EmployeeConfiguration class:
让我们对 EmployeeConfiguration 类执行相同的操作:

using Entities.Models;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore;
using System;


namespace Entities.Configuration
{
    public class EmployeeConfiguration : IEntityTypeConfiguration<Employee>
    { 
        public void Configure(EntityTypeBuilder<Employee> builder) 
        { 
            builder.HasData(
                new Employee { 
                    Id = new Guid("80abbca8-664d-4b20-b5de-024705497d4a"), 
                    Name = "Sam Raiden", 
                    Age = 26, 
                    Position = "Software developer", 
                    CompanyId = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870") 
                }, 
                new Employee { 
                    Id = new Guid("86dba8c0-d178-41e7-938c-ed49778fb52a"), 
                    Name = "Jana McLeaf", 
                    Age = 30, 
                    Position = "Software developer", 
                    CompanyId = new Guid("c9d4c053-49b6-410c-bc78-2d54a9991870") 
                }, 
                new Employee { Id = new Guid("021ca3c1-0deb-4afd-ae94-2159a8479811"), 
                    Name = "Kane Miller", 
                    Age = 35, 
                    Position = "Administrator", 
                    CompanyId = new Guid("3d490a70-94ce-4d15-9494-5248280c2ce3") 
                }); 
        } 
    }
}

To invoke this configuration, we have to change the RepositoryContext class:
要调用此配置,我们必须更改 RepositoryContext 类:

using Entities.Configuration;
using Entities.Models;
using Microsoft.EntityFrameworkCore;
namespace Entities
{
    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; }
    }
}

Now, we can create and apply another migration to seed these data to the database:
现在,我们可以创建并应用另一个迁移,以将这些数据种子设定到数据库中:

PM> Add-Migration InitialData 
Build started...
Build succeeded.
To undo this action, use Remove-Migration.

PM> Update-Database
Build started...
Build succeeded.
Done.

This will transfer all the data from our configuration files to the respective tables.
这会将所有数据从我们的配置文件传输到相应的表中。

3.4 Repository Pattern Logic

After establishing a connection to the database and creating one, it’s time to create a generic repository that will provide us with the CRUD methods. As a result, all the methods can be called upon any repository class in our project.
建立与数据库的连接并创建一个连接后,是时候创建一个通用存储库,该存储库将为我们提供CRUD方法。因此,可以在我们项目中的任何存储库类上调用所有方法。

Furthermore, creating the generic repository and repository classes that use that generic repository is not going to be the final step. We will go a step further and create a wrapper class around repository classes and inject it as a service in a dependency injection container.
此外,创建通用存储库和使用该通用存储库的存储库类不会是最后一步。我们将更进一步,围绕存储库类创建一个包装类,并将其作为服务注入到依赖关系注入容器中。

Consequently, we will be able to instantiate this class once and then call any repository class we need inside any of our controllers.
因此,我们将能够实例化一次此类,然后在任何控制器中调用所需的任何存储库类。

The advantages of this approach will become clearer once we use it in the project.
一旦我们在项目中使用它,这种方法的优势将变得更加清晰。

That said, let’s start by creating an interface for the repository inside the Contracts project:
也就是说,让我们首先为存储库创建一个接口Contracts项目:

using System;
using System.Linq;
using System.Linq.Expressions;
namespace Contracts
{
    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);
    }
}

Right after the interface creation, we are going to create a new Class Library (.NET Core) project with the name Repository and add the reference to the Contracts and Entities class libraries. Inside the Repository project, we are going to create an abstract class RepositoryBase — which is going to implement the IRepositoryBase interface.
在创建接口之后,我们将创建一个名为 Repository 的新类库(.NET Core)项目,并添加对 ContractsEntities 类库的引用。在 Repository 项目中,我们将创建一个抽象类 RepositoryBase — 它将实现 IRepositoryBase 接口。

We need to reference this project from the main project as well.
我们还需要从主项目中引用此项目。

Additional info: We are going to use EF Core functionalities in the Repository project. Therefore, we need to install it inside the Repository project.
其他信息:我们将在存储库项目中使用 EF Core 功能。因此,我们需要将其安装在存储库项目中。

Let’s add the following code to the RepositoryBase class:
让我们将以下代码添加到RepositoryBase类中:

//RepositoryBase.cs
using Contracts;
using Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Repository
{
    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);
    }
}

This abstract class as well as the IRepositoryBase interface works with the generic type T. This type T gives even more reusability to the RepositoryBase class. That means we don’t have to specify the exact model (class) right now for the RepositoryBase to work with. We can do that later on.
此抽象类以及 IRepositoryBase 接口适用于泛型类型 T。此类型 TRepositoryBase 类提供了更多的可重用性。这意味着我们现在不必为RepositoryBase类指定确切的模型(类)。我们可以稍后再这样做。

Moreover, we can see the trackChanges parameter. We are going to use it to improve our read-only query performance. When it’s set to false, we attach the AsNoTracking method to our query to inform EF Core that it doesn’t need to track changes for the required entities. This greatly improves the speed of a query.
此外,我们可以看到 trackChanges 参数。我们将使用它来提高只读查询性能。当它设置为 false 时,我们会将 AsNoTracking 方法附加到查询,以通知 EF Core 它不需要跟踪所需实体的更改。这大大提高了查询的速度。

3.5 Repository User InterFaces and Classes

Now that we have the RepositoryBase class, let’s create the user classes that will inherit this abstract class.
现在我们有了 RepositoryBase 类,让我们创建将继承此抽象类的用户类。

By inheriting from the RepositoryBase class, they will have access to all the methods from it. Furthermore, every user class will have its interface for additional model-specific methods.
通过从 RepositoryBase 类继承,他们将有权访问该类中的所有方法。此外,每个用户类都将具有用于其他特定于模型的方法的接口。

This way, we are separating the logic that is common for all our repository user classes and also specific for every user class itself.
通过这种方式,我们将所有存储库用户类通用的逻辑以及每个用户类本身的逻辑分开。

Let’s create the interfaces in the Contracts project for the Company and Employee classes.
让我们在 Contracts 项目中为 Company 创建接口,然后 Employee 类。

namespace Contracts
{
    public interface ICompanyRepository { }
}

namespace Contracts
{
    public interface IEmployeeRepository { }
}

After this, we can create repository user classes in the Repository project.
在此之后,我们可以在 Repository 中创建存储库用户类。

The first thing we are going to do is to create the CompanyRepository class:
我们要做的第一件事是创建CompanyRepository类:

using Contracts;
using Entities.Models;
using Entities;
namespace Repository
{
    public class CompanyRepository : RepositoryBase<Company>, ICompanyRepository
    { 
        public CompanyRepository(RepositoryContext repositoryContext) : base(repositoryContext) { } 
    }
}
```
And then, the **EmployeeRepository** class:  
然后,**EmployeeRepository** 类:
```
using Contracts;
using Entities.Models;
using Entities;
namespace Repository
{
    public class EmployeeRepository : RepositoryBase<Employee>, IEmployeeRepository { 
        public EmployeeRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }
    }
}

After these steps, we are finished creating the repository and repository user classes. But there are still more things to do.
完成这些步骤后,我们完成了存储库和存储库用户类的创建。但还有更多的事情要做。

3.6 Creating a Repository Manager

It is quite common for the API to return a response that consists of data from multiple resources; for example, all the companies and just some employees older than 30. In such a case, we would have to instantiate both of our repository classes and fetch data from their resources.
API 返回由来自多个资源的数据组成的响应是很常见的;例如,所有公司和一些 30 岁以上的员工。在这种情况下,我们必须实例化两个存储库类,并从其资源中获取数据。‌

Maybe it’s not a problem when we have only two classes, but what if we need the combined logic of five or even more different classes? It would just be too complicated to pull that off.
当我们只有两个类时,也许这不是问题,但是如果我们需要五个甚至更多不同类的组合逻辑呢?要做到这一点太复杂了。

With that in mind, we are going to create a repository manager class, which will create instances of repository user classes for us and then register it inside the dependency injection container. After that, we can inject it inside our controllers (or inside a business layer class, if we have a bigger app) with constructor injection (supported by ASP.NET Core). With the repository manager class in place, we may call any repository user class we need.
考虑到这一点,我们将创建一个存储库管理器类,它将为我们创建存储库用户类的实例,然后在依赖注入容器中注册它。之后,我们可以将其注入控制器(或者如果我们有更大的应用程序,则在业务层类中)使用构造函数注入(由 ASP.NET Core支持)。有了存储库管理器类,我们可以调用我们需要的任何存储库用户类。

But we are also missing one important part. We have the Create, Update, and Delete methods in the RepositoryBase class, but they won’t make any change in the database until we call the SaveChanges method. Our repository manager class will handle that as well.
但我们也缺少一个重要部分。我们在 RepositoryBase 类中有 CreateUpdateDelete 方法,但在我们调用 SaveChanges 方法之前,它们不会在数据库中进行任何更改。我们的存储库管理器类也将处理这个问题。

That said, let’s get to it and create a new interface in the Contract project:
也就是说,让我们开始使用它,并在Contract 项目中创建一个新接口:

namespace Contracts
{
    public interface IRepositoryManager { 
        ICompanyRepository Company { get; } 
        IEmployeeRepository Employee { get; } 
        void Save(); }
}

And add a new class to the Repository project:
并向 Repository 项目添加一个新类:

using Contracts;
using Entities;
namespace Repository
{
    public class RepositoryManager : IRepositoryManager
    {
        private RepositoryContext _repositoryContext;
        private ICompanyRepository _companyRepository;
        private IEmployeeRepository _employeeRepository;
        public RepositoryManager(RepositoryContext repositoryContext)
        {
            _repositoryContext = repositoryContext;
        }
        public ICompanyRepository Company
        {
            get
            {
                if (_companyRepository == null)
                    _companyRepository = new CompanyRepository(_repositoryContext);
                return _companyRepository;
            }
        }
        public IEmployeeRepository Employee
        {
            get
            {
                if (_employeeRepository == null)
                    _employeeRepository = new EmployeeRepository(_repositoryContext);
                return _employeeRepository;
            }
        }
        public void Save() => _repositoryContext.SaveChanges();
    }
}

As you can see, we are creating properties that will expose the concrete repositories and also we have the Save() method to be used after all the modifications are finished on a certain object. This is a good practice because now we can, for example, add two companies, modify two employees, and delete one company — all in one action — and then just call the Save method once. All the changes will be applied or if something fails, all the changes will be reverted:
如您所见,我们正在创建将公开具体存储库的属性,并且我们还具有在对某个对象完成所有修改后要使用的 Save() 方法。这是一个很好的做法,因为现在我们可以添加两个公司,修改两个员工,并删除一个公司 – 所有这些都在一个操作中 – 然后只需调用一次 Save 方法。将应用所有更改,或者如果某些内容失败,则所有更改都将被还原:

_repository.Company.Create(company); 
_repository.Company.Create(anotherCompany); 
_repository.Employee.Update(employee); 
_repository.Employee.Update(anotherEmployee);
_repository.Company.Delete(oldCompany);
_repository.Save();

After these changes, we need to register our manager class and add a reference from the Repository to our main project if not already done so. So, let’s first modify the ServiceExtensions class by adding this code:
完成这些更改后,我们需要注册管理器类,并将存储库中的引用添加到主项目中(如果尚未这样做)。因此,让我们首先通过添加以下代码来修改 ServiceExtensions 类:

using Contracts;
using Entities;
using LoggerService;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Repository;

namespace CompanyEmployees.Extensions
{
    public static class ServiceExtensions
    {
        public static void ConfigureCors(this IServiceCollection services) =>
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy", builder =>
                builder.AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());
            });

        public static void ConfigureIISIntegration(this IServiceCollection service) =>
            service.Configure<IISOptions>(options => { });

        public static void ConfigureLoggerService(this IServiceCollection services) => 
            services.AddScoped<ILoggerManager, LoggerManager>();

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

        public static void ConfigureRepositoryManager(this IServiceCollection services) => 
            services.AddScoped<IRepositoryManager, RepositoryManager>();
    }
}

And in the Startup class inside the ConfigureServices method, above the services.AddController() line, we have to add this code:
ConfigureServices 方法内的 Startup 类中,在 services.AddController() 上方行,我们必须添加以下代码:

public void ConfigureServices(IServiceCollection services)
        {
            services.ConfigureCors();
            services.ConfigureIISIntegration();
            services.ConfigureLoggerService();
            services.ConfigureSqlContext(Configuration);
            services.ConfigureRepositoryManager();

            services.AddControllers();
        }

Excellent.
非常好。

As soon as we add some methods to the specific repository classes, we are going to be able to test this logic, but we can just take a peek at how we can inject and use this repository manager.
一旦我们将一些方法添加到特定的存储库类中,我们将能够测试此逻辑,但是我们可以看一下如何注入和使用此存储库管理器。

All we have to do is to inject the RepositoryManager service inside the controller and we are going to see the Company and Employee properties that will provide us access to the specific repository methods:
我们所要做的就是在控制器中注入 RepositoryManager 服务,我们将看到 Company 和 Employee 属性,这些属性将使我们能够访问特定的存储库方法:

using Contracts;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

namespace CompanyEmployees
{
    //public class WeatherForecast
    //{
    //    public DateTime Date { get; set; }
    //    public int TemperatureC { get; set; }
    //    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    //    public string Summary { get; set; }
    //}

    //[Route("[controller]")]
    //[ApiController]
    //public class WeatherForecastController : ControllerBase
    //{
    //    private ILoggerManager _logger;
    //    public WeatherForecastController(ILoggerManager logger) { _logger = logger; }
    //    [HttpGet]
    //    public IEnumerable<string> Get()
    //    {
    //        _logger.LogInfo("Here is info message from our values controller.");
    //        _logger.LogDebug("Here is debug message from our values controller.");
    //        _logger.LogWarn("Here is warn message from our values controller.");
    //        _logger.LogError("Here is an error message from our values controller.");
    //        return new string[] { "value1", "value2" };
    //    }
    //}

    [Route("[controller]")]
    [ApiController]
    public class WeatherForecastController : ControllerBase
    {
        private readonly IRepositoryManager _repository;
        public WeatherForecastController(IRepositoryManager repository)
        {
            _repository = repository;
        }
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            //_repository.Company.AnyMethodFromCompanyRepository();
            //_repository.Employee.AnyMethodFromEmployeeRepository();
            return new string[] { "value1", "value2" };
        }
    }

}

We did an excellent job here. The repository layer is prepared and ready to be used to fetch data from the database.
我们在这里做得很好。存储库层已准备就绪,可用于从数据库获取数据。

As you can see, we have injected our repository inside the controller; this is a good practice for an application of this size. But for larger-scale applications, we would create an additional business layer between our controllers and repository logic and our RepositoryManager service would be injected inside that Business layer — thus freeing the controller from repository logic.
如您所见,我们已经在控制器中注入了我们的存储库;对于这种大小的应用程序,这是一个很好的做法。但对于更大规模的应用程序,我们将在控制器和存储库逻辑之间创建一个额外的业务层,并且我们的 RepositoryManager 服务将注入该业务层中,从而将控制器从存储库逻辑中解放出来。

Now, we can continue towards handling Get requests in our application.
现在,我们可以继续处理应用程序中的 Get 请求。