EF Multi-tenant instance: how to quickly implement and support multiple DbContext

Foreword

Previous essays we talked about multi-tenant model, an example of evolution through the multi-tenant model. Broadly grouped and summarized the manifestation of several models.

And passing reference to the separate read and write.

By adjusting the code for several times, make the library more versatile. Today we talk about how fast multi-tenant access through the library.

Library Address:

https://github.com/woailibain/kiwiho.EFcore.MultiTenant

 

Implement

The code example, the above references entirely github address  traditional_and_multiple_context  example.

From the name of the instance may know, our main demo typical multiple sets of user mode, and supports multiple DbContext in the same system

 

Why a system to support multiple DbContext

In fact, to answer this question or to return to your system why would the tenant mode. Is nothing more than system performance bottlenecks, data security and isolate the problem.

1. System performance issues, and that system is the result of a long period of baptism, that is the process of multi-tenant system architecture evolution. Previous systems based monomer, a database system.

To evolution, certainly it is a process, so will a database segmented by type of business into multiple databases is the logical thing.

2. Data security and isolation, in fact, data security and isolation, does not require all data isolation. For example, some companies may only own customer information isolation, may only isolate sensitive data.

Then we can be big business for several separate modules, the sensitive data using a database separate data isolation mode, do not isolate sensitive data by data mode, save resources.

 

Project structure

Let's take a look at the project structure. There were two items: one is Api, another DAL.

Here it comes to a question, why separate Api and DAL. In fact, the project is to simulate today's mainstream project structure, at least the data layer and logical operations are separate.

Api structure and references, see references Api almost all packages MultiTenant and comprises DAL.

In fact, **** here. MySql, ***. SqlServer and ****. Postgre three packages, only a reference to, for this example is the use of three databases only need to refer to multiple .

 

 

DAL structure and references cited DAL is relatively simple, only need to refer to DAL and Model

 

 

Detailed implementation

Detailed DAL

DAL Since it is the data layer, and then DbContext Entity is a must. Here while CustomerDbContext and StoreDbContext.

We first look at StoreDbContext, its main products include Product table.

There are a few key points:

1. StoreDbContext must be inherited from TenantBaseDbContext

2. The first constructor parameter options, need to use the generic DbContextOptions <> type incoming. (If the entire system is only one DbContext, this can be used instead of DbContextOptions)

3. Rewrite OnModelCreating method. And this is not a necessary step. However, most need DbContext method defined by the database entity structure, so if there override this method, you must explicitly call base.OnModelCreating 

4. properties disclosed Products, representative of the product table.

 1     public class StoreDbContext : TenantBaseDbContext
 2     {
 3         public DbSet<Product> Products => this.Set<Product>();
 4         
 5         public StoreDbContext(DbContextOptions<StoreDbContext> options, TenantInfo tenant, IServiceProvider serviceProvider) 
 6             : base(options, tenant, serviceProvider)
 7         {
 8             
 9         }
10 
11         protected override void OnModelCreating(ModelBuilder modelBuilder)
12         {
13             base.OnModelCreating(modelBuilder);
14         }
15     }

Now look CustomerDbContext, which mainly contains Instruction Orders table

As used herein, the streamlined DbContext implementation, contains only public constructors and properties Instructions

1     public class CustomerDbContext : TenantBaseDbContext
2     {
3         public DbSet<Instruction> Instructions => this.Set<Instruction>();
4         public CustomerDbContext(DbContextOptions<CustomerDbContext> options, TenantInfo tenant, IServiceProvider serviceProvider) 
5             : base(options, tenant, serviceProvider)
6         {
7         }
8     }

The remaining two categories are Product and Instruction. They do not have anything special, that is, ordinary Entity

 1     public class Product
 2     {
 3         [Key]
 4         public int Id { get; set; } 
 5 
 6         [StringLength(50), Required]
 7         public string Name { get; set; }
 8 
 9         [StringLength(50)]
10         public string Category { get; set; }
11 
12         public double? Price { get; set; }
13     }
Product
 1     public class Instruction
 2     {
 3         [Key]
 4         public int Id { get; set; }
 5 
 6         public DateTime Date { get; set; }
 7 
 8         public double TotalAmount { get; set; }
 9 
10         [StringLength(200)]
11         public string Remark { get; set; }
12 
13     }
Instruction

 

Detailed Api

Startup

Startup as a configuration entry asp.net core, we take a look here

The first is ConfigureService method. Here the main configuration service you want to use and registration

1. We AddMySqlPerConnection extension function, add StoreDbContext use, the separation between the database used by the tenant data pattern

Arranged inside ConnectionPerfix, representative of the profile connection string mysql_ prefix may be provided for use StoreDbContext.

2. AddMySqlPerTable extension function, the use of CustomerDbContext added using separate tables is to use the data patterns among tenants.

The first parameter is the configuration of the multi-tenant key, is used herein customer, note that in the case where a plurality of DbContext, which must contain a key DbContext

The second parameter is the key link string configuration, since the simultaneous use of a plurality of tenant database, so there only needs one link string

 

It may be noted here, we can provide a default configuration 2 way multi-tenant, it was commissioned and parameters .

They use two methods are different, all while supporting these two modes in different modes

 1         public void ConfigureServices(IServiceCollection services)
 2         {
 3             // MySql
 4             services.AddMySqlPerConnection<StoreDbContext>(settings=>
 5             {
 6                 settings.ConnectionPrefix = "mysql_";
 7             });
 8 
 9             services.AddMySqlPerTable<CustomerDbContext>("customer","mysql_default_customer");
10 
11             services.AddControllers();
12         }

Followed by Configure method. This is mainly configured asp.net core of request pipeline

1. You can see the use of several asp.net core middleware, which UseRouting and UseEndpoint is necessary.

2. Use UserMiddleware extension functions into our middleware TenantInfoMiddleware. This is the default middleware support library provides.

 1         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 2         {
 3             if (env.IsDevelopment())
 4             {
 5                 app.UseDeveloperExceptionPage();
 6             }
 7 
 8             app.UseMiddleware<TenantInfoMiddleware>();
 9 
10 
11             app.UseRouting();
12 
13             app.UseEndpoints(endpoints =>
14             {
15                 endpoints.MapControllers();
16             });
17         }

appsettings

Appsettings modify this file, mainly to add a link to the string inside

 1 {
 2   "Logging": {
 3     "LogLevel": {
 4       "Default": "Information",
 5       "Microsoft": "Warning",
 6       "Microsoft.Hosting.Lifetime": "Information"
 7     }
 8   },
 9   "AllowedHosts": "*",
10   "ConnectionStrings":{
11     "mysql_default":"server=127.0.0.1;port=3306;database=multi_tenant_default;uid=root;password=gh001;charset=utf8mb4",
12     "mysql_store1":"server=127.0.0.1;port=3306;database=multi_tenant_store1;uid=root;password=gh001;charset=utf8mb4",
13     "mysql_store2":"server=127.0.0.1;port=3306;database=multi_tenant_store2;uid=root;password=gh001;charset=utf8mb4",
14 
15 "mysql_default_customer":"server=127.0.0.1;port=3306;database=multi_tenant_customer;uid=root;password=gh001;charset=utf8mb4"
16   }
17 }

 

ProductController 和 InstructionController

productController and InstructionController very similar to them which mainly contains three methods, namely: all queries, according to Id query, add

Inside the code is not an explanation of

 1 namespace kiwiho.EFcore.MultiTenant.Example.Api.Controllers
 2 {
 3     [ApiController]
 4     [Route("api/[controller]s")]
 5     public class ProductController : ControllerBase
 6     {
 7         private readonly StoreDbContext storeDbContext;
 8 
 9         public ProductController(StoreDbContext storeDbContext)
10         {
11             this.storeDbContext = storeDbContext;
12             this.storeDbContext.Database.EnsureCreated();
13             
14             // this.storeDbContext.Database.Migrate();
15         }
16 
17         [HttpPost("")]
18         public async Task<ActionResult<Product>> Create(Product product)
19         {
20             var rct = await this.storeDbContext.Products.AddAsync(product);
21 
22             await this.storeDbContext.SaveChangesAsync();
23 
24             return rct?.Entity;
25 
26         }
27 
28         [HttpGet("{id}")]
29         public async Task<ActionResult<Product>> Get([FromRoute] int id)
30         {
31 
32             var rct = await this.storeDbContext.Products.FindAsync(id);
33 
34             return rct;
35 
36         }
37 
38         [HttpGet("")]
39         public async Task<ActionResult<List<Product>>> Search()
40         {
41             var rct = await this.storeDbContext.Products.ToListAsync();
42             return rct;
43         }
44     }
45 }
ProductController
 1 namespace kiwiho.EFcore.MultiTenant.Example.Api.Controllers
 2 {
 3     [ApiController]
 4     [Route("api/[controller]s")]
 5     public class InstructionController : ControllerBase
 6     {
 7         private readonly CustomerDbContext customerDbContext;
 8         public InstructionController(CustomerDbContext customerDbContext)
 9         {
10             this.customerDbContext = customerDbContext;
11             this.customerDbContext.Database.EnsureCreated();
12         }
13 
14         [HttpPost("")]
15         public async Task<ActionResult<Instruction>> Create(Instruction instruction)
16         {
17             var rct = await this.customerDbContext.Instructions.AddAsync(instruction);
18 
19             await this.customerDbContext.SaveChangesAsync();
20 
21             return rct?.Entity;
22 
23         }
24 
25         [HttpGet("{id}")]
26         public async Task<ActionResult<Instruction>> Get([FromRoute] int id)
27         {
28 
29             var rct = await this.customerDbContext.Instructions.FindAsync(id);
30 
31             return rct;
32 
33         }
34 
35         [HttpGet("")]
36         public async Task<ActionResult<List<Instruction>>> Search()
37         {
38             var rct = await this.customerDbContext.Instructions.ToListAsync();
39             return rct;
40         }
41     }
42 }
InstructionController

 

Implementation of generalization

Implementation process, we made a total of four things:

1. Definitions and DbContext corresponding Entity. DbContext must inherit TenantBaseDbContext.

2. Modify the Startup, configuration, multi-tenant services, middleware, multi-tenant configuration you want to use.

3. Follow the rule to add the string. 

4. Add Controller.

 

test result

Before the test results, we need some raw data. Api can be inserted or call by generating a database

Product data query using store1

 

Product data query using store2

 

 

 Use of data store1 query Instruction

 

Use of data store2 query Instruction

 

 

 

to sum up

Through the above steps, we can see that we can have a simple configuration, it is multi-tenant embodiment mode.

 

What this example defects:

We should be able to find instances Store and Customer are used store1 and store2 to request data. However, the Customer field, obviously need to customer1 and the like customers2 request data.

This example is mainly for simplicity and clarity, will confuse them.

However, to resolve this matter, it is within reach. We will continue in future articles.

 

After there will be any examples:

Since the essay referred to the evolution and separate read and write multi-tenant, then we will give priority talked about this part.

By looking at github source code, some people may doubt, in addition to MySql, SqlServer and Postgre, is not it can not support the other database.

Actually not, the library has already done a certain class scalability, they can use their own extension methods UseOracle such as the Oracle integrated in, in less than 10 lines of code.

 

 

Look how the code:

All code has been updated to github, wherein the code in the examples herein example / traditional_and_multiple_context  the

https://github.com/woailibain/kiwiho.EFcore.MultiTenant

 

Guess you like

Origin www.cnblogs.com/woailibian/p/12464858.html