Skip to content

Commit f36e10b

Browse files
committed
Finalize the Entity-Framework-Core-Migrations guide.
1 parent ec0895f commit f36e10b

File tree

8 files changed

+175
-20
lines changed

8 files changed

+175
-20
lines changed

docs/en/Entity-Framework-Core-Migrations.md

+148-7
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,19 @@ When you [create a new web application](https://abp.io/get-started) (with EF Cor
2020

2121
![bookstore-visual-studio-solution-v3](images/bookstore-visual-studio-solution-v3.png)
2222

23-
> Actual solution structure may be a bit different based on your preferences, but the database part will be same.
23+
Actual solution structure may be a bit different based on your preferences, but the database part will be same.
24+
25+
> This document will use the `Acme.BookStore` example project name to refer the projects and classes. You need to find the corresponding class/project in your solution.
2426
2527
### The Database Structure
2628

27-
The startup template has some [application modules](Modules/Index.md) pre-installed. Each layer of the solution has corresponding module package references. So, the `.EntityFrameworkCore` project has the NuGet references for the `.EntityFrameworkCore` packages of the used modules:
29+
The startup template has some [application modules](Modules/Index.md) pre-installed. Each layer of the solution has corresponding module **package references**. So, the `.EntityFrameworkCore` project has the NuGet references for the `.EntityFrameworkCore` packages of the used modules:
2830

2931
![bookstore-efcore-dependencies](images/bookstore-efcore-dependencies.png)
3032

31-
In this way, you collect all the EF Core dependencies under the `.EntityFrameworkCore` project.
33+
In this way, you collect all the **EF Core dependencies** under the `.EntityFrameworkCore` project.
3234

33-
> In addition to the module references, it references to the `Volo.Abp.EntityFrameworkCore.SqlServer` package since the startup template is pre-configured for the SQL Server. See the documentation if you want to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md).
35+
> In addition to the module references, it references to the `Volo.Abp.EntityFrameworkCore.SqlServer` package since the startup template is pre-configured for the **SQL Server**. See the documentation if you want to [switch to another DBMS](Entity-Framework-Core-Other-DBMS.md).
3436
3537
While every module has its own `DbContext` class by design and can use its **own physical database**, the solution is configured to use a **single shared database** as shown in the figure below:
3638

@@ -57,13 +59,13 @@ ABP Framework's [connection string](Connection-Strings.md) system allows you to
5759

5860
The example configuration about tells to the ABP Framework to use the second connection string for the [Audit Logging module](Modules/Audit-Logging.md).
5961

60-
However, this is just the beginning. You also need to create the second database, create audit log tables inside it and maintain the database tables using the code first approach. One of the main purposes of this document is to guide you on such database separation scenarios.
62+
**However, this is just the beginning**. You also need to create the second database, create audit log tables inside it and maintain the database tables using the code first migrations approach. One of the main purposes of this document is to guide you on such **database separation** scenarios.
6163

6264
#### Module Tables
6365

6466
Every module uses its own databases tables. For example, the [Identity Module](Modules/Identity.md) has some tables to manage the users and roles in the system.
6567

66-
#### Table Prefixes
68+
##### Table Prefixes
6769

6870
Since it is allowed to share a single database by all modules (it is the default configuration), a module typically uses a prefix to group its own tables.
6971

@@ -661,7 +663,9 @@ You can just copy & modify the content of the original `.DbMigrations` project.
661663
Create a new DbContext for the migrations and call the extension methods to configure database tables for the related modules:
662664

663665
````csharp
664-
public class BookStoreSecondMigrationsDbContext : AbpDbContext<BookStoreSecondMigrationsDbContext>
666+
[ConnectionStringName("AbpPermissionManagement")]
667+
public class BookStoreSecondMigrationsDbContext :
668+
AbpDbContext<BookStoreSecondMigrationsDbContext>
665669
{
666670
public BookStoreSecondMigrationsDbContext(
667671
DbContextOptions<BookStoreSecondMigrationsDbContext> options)
@@ -682,6 +686,8 @@ public class BookStoreSecondMigrationsDbContext : AbpDbContext<BookStoreSecondMi
682686
}
683687
````
684688

689+
> `[ConnectionStringName(...)]` attribute is important here and tells to the ABP Framework which connection string should be used for this `DbContext`.
690+
685691
Create a Design Time Db Factory class, that is used by the EF Core tooling (by `Add-Migration` and `Update-Database` PCM commands for example):
686692

687693
````csharp
@@ -778,3 +784,138 @@ Run the `Update-Database` command to delete the tables from your main database.
778784

779785
Notice that you've also **deleted some initial seed data** (for example, permission grants for the admin role) if you haven't copied it to the new database. If you run the application, you may not login anymore. The solution is simple: **Re-run the `.DbMigrator` console application** in your solution, it will seed the new database.
780786

787+
### Automate the Second Database Schema Migration
788+
789+
`.DbMigrator` console application can run the seed code across multiple databases, without any additional configuration. However, it can not run the EF Core Code First Migrations inside the second database migration project. Now, you will see how to configure the console migration application to handle both databases.
790+
791+
#### Implementing the IBookStoreDbSchemaMigrator
792+
793+
`EntityFrameworkCoreBookStoreDbSchemaMigrator` class inside the `Acme.BookStore.EntityFrameworkCore.DbMigrations` project is responsible to migrate the database schema for the `BookStoreMigrationsDbContext`. It should be like that:
794+
795+
````csharp
796+
[Dependency(ReplaceServices = true)]
797+
public class EntityFrameworkCoreBookStoreDbSchemaMigrator
798+
: IBookStoreDbSchemaMigrator, ITransientDependency
799+
{
800+
private readonly IServiceProvider _serviceProvider;
801+
802+
public EntityFrameworkCoreBookStoreDbSchemaMigrator(
803+
IServiceProvider serviceProvider)
804+
{
805+
_serviceProvider = serviceProvider;
806+
}
807+
808+
public async Task MigrateAsync()
809+
{
810+
/* We intentionally resolving the BookStoreMigrationsDbContext
811+
* from IServiceProvider (instead of directly injecting it)
812+
* to properly get the connection string of the current tenant in the
813+
* current scope.
814+
*/
815+
816+
await _serviceProvider
817+
.GetRequiredService<BookStoreMigrationsDbContext>()
818+
.Database
819+
.MigrateAsync();
820+
}
821+
}
822+
````
823+
824+
It implements the `IBookStoreDbSchemaMigrator` and replaces existing services (see the first line).
825+
826+
Remove the `[Dependency(ReplaceServices = true)]` line, because we will have two implementations of this interface and we want to use both. We don't want to replace one of them.
827+
828+
Create a copy of this class inside the new migration project (`Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb`), but use the `BookStoreSecondMigrationsDbContext`. Example implementation:
829+
830+
````csharp
831+
public class EntityFrameworkCoreSecondBookStoreDbSchemaMigrator
832+
: IBookStoreDbSchemaMigrator, ITransientDependency
833+
{
834+
private readonly IServiceProvider _serviceProvider;
835+
836+
public EntityFrameworkCoreSecondBookStoreDbSchemaMigrator(
837+
IServiceProvider serviceProvider)
838+
{
839+
_serviceProvider = serviceProvider;
840+
}
841+
842+
public async Task MigrateAsync()
843+
{
844+
/* We intentionally resolving the BookStoreSecondMigrationsDbContext
845+
* from IServiceProvider (instead of directly injecting it)
846+
* to properly get the connection string of the current tenant in the
847+
* current scope.
848+
*/
849+
850+
await _serviceProvider
851+
.GetRequiredService<BookStoreSecondMigrationsDbContext>()
852+
.Database
853+
.MigrateAsync();
854+
}
855+
}
856+
````
857+
858+
> Name of this class is important for [dependency injection](Dependency-Injection.md). It should end with `BookStoreDbSchemaMigrator` to be injectable by `IBookStoreDbSchemaMigrator` reference.
859+
860+
We, now, have two implementations of the `IBookStoreDbSchemaMigrator` interface, each one responsible to migrate the related database schema.
861+
862+
#### Define a Module Class for the Second Migration Project
863+
864+
It is time to define the module class for this second migrations (`Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb`) project:
865+
866+
````csharp
867+
[DependsOn(
868+
typeof(BookStoreEntityFrameworkCoreModule)
869+
)]
870+
public class BookStoreEntityFrameworkCoreSecondDbMigrationsModule : AbpModule
871+
{
872+
public override void ConfigureServices(ServiceConfigurationContext context)
873+
{
874+
context.Services.AddAbpDbContext<BookStoreSecondMigrationsDbContext>();
875+
}
876+
}
877+
````
878+
879+
Now, reference `Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb` project from the `Acme.BookStore.DbMigrator` project and `typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule)` to the dependency list of the `BookStoreDbMigratorModule`. `BookStoreDbMigratorModule` class should be something like that:
880+
881+
````csharp
882+
[DependsOn(
883+
typeof(AbpAutofacModule),
884+
typeof(BookStoreEntityFrameworkCoreDbMigrationsModule),
885+
typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule), // ADDED THIS!
886+
typeof(BookStoreApplicationContractsModule)
887+
)]
888+
public class BookStoreDbMigratorModule : AbpModule
889+
{
890+
...
891+
}
892+
````
893+
894+
We had a reference to the `Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb` project from the `Acme.BookStore.Web` project, but hadn't added module dependency since we hadn't created it yet. But, now we have it and we need to add `typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule)` to the dependency list of the `BookStoreWebModule` class.
895+
896+
#### BookStoreDbMigrationService
897+
898+
You need one final touch to the `BookStoreDbMigrationService` inside the `Acme.BookStore.Domain` project. It is currently designed to work with a single `IBookStoreDbSchemaMigrator` implementation, but now we have two.
899+
900+
It injects `IBookStoreDbSchemaMigrator`. Replace it with an `IEnumerable<IBookStoreDbSchemaMigrator>` injection.
901+
902+
Now, you have a collection of migrators instead of a single one. Find the lines like:
903+
904+
````csharp
905+
await _dbSchemaMigrators.MigrateAsync();
906+
````
907+
908+
change them to:
909+
910+
````csharp
911+
foreach (var migrator in _dbSchemaMigrators)
912+
{
913+
await migrator.MigrateAsync();
914+
}
915+
````
916+
917+
You can run the `.DbMigrator` application to migrate & seed the databases. To test, you can delete both databases and run the application to see if they are created.
918+
919+
## Conclusion
920+
921+
This document explains how to split your databases and manage your database migrations of your solution for Entity Framework Core. In brief, you need to have a separate migration project per different databases.

samples/EfCoreMigrationDemo/src/Acme.BookStore.DbMigrator/Acme.BookStore.DbMigrator.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<ItemGroup>
2929
<PackageReference Include="Volo.Abp.Autofac" Version="2.1.1" />
3030
<ProjectReference Include="..\Acme.BookStore.Application.Contracts\Acme.BookStore.Application.Contracts.csproj" />
31+
<ProjectReference Include="..\Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb\Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb.csproj" />
3132
<ProjectReference Include="..\Acme.BookStore.EntityFrameworkCore.DbMigrations\Acme.BookStore.EntityFrameworkCore.DbMigrations.csproj" />
3233
</ItemGroup>
3334

samples/EfCoreMigrationDemo/src/Acme.BookStore.DbMigrator/BookStoreDbMigratorModule.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Acme.BookStore.EntityFrameworkCore;
1+
using Acme.BookStore.DbMigrationsForSecondDb.EntityFrameworkCore;
2+
using Acme.BookStore.EntityFrameworkCore;
23
using Volo.Abp.Autofac;
34
using Volo.Abp.BackgroundJobs;
45
using Volo.Abp.Modularity;
@@ -8,6 +9,7 @@ namespace Acme.BookStore.DbMigrator
89
[DependsOn(
910
typeof(AbpAutofacModule),
1011
typeof(BookStoreEntityFrameworkCoreDbMigrationsModule),
12+
typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule),
1113
typeof(BookStoreApplicationContractsModule)
1214
)]
1315
public class BookStoreDbMigratorModule : AbpModule

samples/EfCoreMigrationDemo/src/Acme.BookStore.Domain/Data/BookStoreDbMigrationService.cs

+14-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Linq;
1+
using System.Collections.Generic;
32
using System.Threading.Tasks;
43
using Microsoft.Extensions.Logging;
54
using Microsoft.Extensions.Logging.Abstractions;
@@ -15,18 +14,18 @@ public class BookStoreDbMigrationService : ITransientDependency
1514
public ILogger<BookStoreDbMigrationService> Logger { get; set; }
1615

1716
private readonly IDataSeeder _dataSeeder;
18-
private readonly IBookStoreDbSchemaMigrator _dbSchemaMigrator;
17+
private readonly IEnumerable<IBookStoreDbSchemaMigrator> _dbSchemaMigrators;
1918
private readonly ITenantRepository _tenantRepository;
2019
private readonly ICurrentTenant _currentTenant;
2120

2221
public BookStoreDbMigrationService(
2322
IDataSeeder dataSeeder,
24-
IBookStoreDbSchemaMigrator dbSchemaMigrator,
23+
IEnumerable<IBookStoreDbSchemaMigrator> dbSchemaMigrators,
2524
ITenantRepository tenantRepository,
2625
ICurrentTenant currentTenant)
2726
{
2827
_dataSeeder = dataSeeder;
29-
_dbSchemaMigrator = dbSchemaMigrator;
28+
_dbSchemaMigrators = dbSchemaMigrators;
3029
_tenantRepository = tenantRepository;
3130
_currentTenant = currentTenant;
3231

@@ -59,7 +58,11 @@ public async Task MigrateAsync()
5958
private async Task MigrateHostDatabaseAsync()
6059
{
6160
Logger.LogInformation("Migrating host database schema...");
62-
await _dbSchemaMigrator.MigrateAsync();
61+
62+
foreach (var migrator in _dbSchemaMigrators)
63+
{
64+
await migrator.MigrateAsync();
65+
}
6366

6467
Logger.LogInformation("Executing host database seed...");
6568
await _dataSeeder.SeedAsync();
@@ -70,7 +73,11 @@ private async Task MigrateHostDatabaseAsync()
7073
private async Task MigrateTenantDatabasesAsync(Tenant tenant)
7174
{
7275
Logger.LogInformation($"Migrating schema for {tenant.Name} database...");
73-
await _dbSchemaMigrator.MigrateAsync();
76+
77+
foreach (var migrator in _dbSchemaMigrators)
78+
{
79+
await migrator.MigrateAsync();
80+
}
7481

7582
Logger.LogInformation($"Executing {tenant.Name} tenant database seed...");
7683
await _dataSeeder.SeedAsync(tenant.Id);

samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/EntityFrameworkCoreBookStoreDbSchemaMigrator.cs

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
namespace Acme.BookStore.EntityFrameworkCore
99
{
10-
[Dependency(ReplaceServices = true)]
1110
public class EntityFrameworkCoreBookStoreDbSchemaMigrator
1211
: IBookStoreDbSchemaMigrator, ITransientDependency
1312
{

samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb/EntityFrameworkCore/BookStoreSecondMigrationsDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
using Microsoft.EntityFrameworkCore;
22
using Volo.Abp.AuditLogging.EntityFrameworkCore;
3+
using Volo.Abp.Data;
34
using Volo.Abp.EntityFrameworkCore;
45
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
56
using Volo.Abp.SettingManagement.EntityFrameworkCore;
67

78
namespace Acme.BookStore.DbMigrationsForSecondDb.EntityFrameworkCore
89
{
10+
[ConnectionStringName("AbpPermissionManagement")]
911
public class BookStoreSecondMigrationsDbContext : AbpDbContext<BookStoreSecondMigrationsDbContext>
1012
{
1113
public BookStoreSecondMigrationsDbContext(

samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb/EntityFrameworkCore/EntityFrameworkCoreBookStoreSecondDbSchemaMigrator.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
using System;
22
using System.Threading.Tasks;
3+
using Acme.BookStore.Data;
34
using Microsoft.EntityFrameworkCore;
45
using Microsoft.Extensions.DependencyInjection;
56
using Volo.Abp.DependencyInjection;
67

78
namespace Acme.BookStore.DbMigrationsForSecondDb.EntityFrameworkCore
89
{
9-
[Dependency(ReplaceServices = true)]
10-
public class EntityFrameworkCoreBookStoreSecondDbSchemaMigrator : ITransientDependency
10+
public class EntityFrameworkCoreSecondBookStoreDbSchemaMigrator
11+
: IBookStoreDbSchemaMigrator, ITransientDependency
1112
{
1213
private readonly IServiceProvider _serviceProvider;
1314

14-
public EntityFrameworkCoreBookStoreSecondDbSchemaMigrator(
15+
public EntityFrameworkCoreSecondBookStoreDbSchemaMigrator(
1516
IServiceProvider serviceProvider)
1617
{
1718
_serviceProvider = serviceProvider;
1819
}
1920

2021
public async Task MigrateAsync()
2122
{
22-
/* We intentionally resolving the BookStoreMigrationsDbContext
23+
/* We intentionally resolving the BookStoreSecondMigrationsDbContext
2324
* from IServiceProvider (instead of directly injecting it)
2425
* to properly get the connection string of the current tenant in the
2526
* current scope.

samples/EfCoreMigrationDemo/src/Acme.BookStore.Web/BookStoreWebModule.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using Acme.BookStore.DbMigrationsForSecondDb.EntityFrameworkCore;
34
using Localization.Resources.AbpUi;
45
using Microsoft.AspNetCore;
56
using Microsoft.AspNetCore.Builder;
@@ -43,6 +44,7 @@ namespace Acme.BookStore.Web
4344
typeof(BookStoreHttpApiModule),
4445
typeof(BookStoreApplicationModule),
4546
typeof(BookStoreEntityFrameworkCoreDbMigrationsModule),
47+
typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule),
4648
typeof(AbpAutofacModule),
4749
typeof(AbpIdentityWebModule),
4850
typeof(AbpAccountWebIdentityServerModule),

0 commit comments

Comments
 (0)