mirror of https://github.com/abpframework/abp.git
2 changed files with 209 additions and 0 deletions
@ -0,0 +1,209 @@ |
|||||
|
# Unifying DbContexts for EF Core / Removing the EF Core Migrations Project |
||||
|
|
||||
|
This article shows how to remove the `EntityFrameworkCore.DbMigrations` project from your solution to have a single `DbContext` for your database mappings and code first migrations. |
||||
|
|
||||
|
## Motivation |
||||
|
|
||||
|
If you create a new solution with **Entity Framework Core** as the database provider, you see two projects related to EF Core: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
* `EntityFrameworkCore` project contains the actual `DbContext` of your application. It includes all the database mappings and your repository implementations. |
||||
|
* `EntityFrameworkCore.DbMigrations` project, on the other hand, contains another `DbContext` class that is only used to create and apply the database migrations. It contains the database mappings for all the modules you are using, so have a single, unified database schema. |
||||
|
|
||||
|
There were two main reasons we'd created such a separation; |
||||
|
|
||||
|
1. Your actual `DbContext` remains simple and focused. It only contains your own entities and doesn't contain anything related to the modules that are used by the application. |
||||
|
2. You can create your own classes that map to the tables of depending modules. For example, the `AppUser` entity (that is included in the downloaded solution) is mapped to `AbpUsers` table in the database, which is actually mapped to the `IdentityUser` entity of the [Identity Module](https://docs.abp.io/en/abp/latest/Modules/Identity). That means they share the same database table. `AppUser` includes less properties compared to `IdentityServer`. You only add the properties you need, not more. This also allows you to add new standard (type-safe) properties to the `AppUser` for your custom requirements as long as you carefully manage the database mappings. |
||||
|
|
||||
|
We've [documented the structure](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Migrations) in details. However, it has always been a problem for the developers since that structure makes your database mappings complicated when you re-use tables of the depended modules. Many developers are misunderstanding or making mistakes while mapping such classes, especially when they try to use these entities in relations to other entities. |
||||
|
|
||||
|
**So, [we've decided](https://github.com/abpframework/abp/issues/8776) to cancel that separation and remove the `EntityFrameworkCore.DbMigrations` project in the version 4.4. New startup solutions will come with a single `EntityFrameworkCore` project and single `DbContext` class.** |
||||
|
|
||||
|
If you want to make it in your solution with today, follow the steps in this article. |
||||
|
|
||||
|
> There is one **drawback** with the new design (everything in software development is a trade-off). We need to remove the `AppUser` entity, because EF Core can't map two classes to single table without an inheritance relation. I will cover this later in this article and provide suggestions to deal with it. |
||||
|
|
||||
|
> If you are using **ABP Commercial**, ABP Suite code generation won't work correctly with the new design. In this case, we suggest to wait for the next version. |
||||
|
|
||||
|
## All the Changes in one PR! |
||||
|
|
||||
|
I've created a new solution with v4.3, then made all the changes in a pull request, so you can see all the changes line by line. While this article will cover all, you may want to check the changes done in this PR **(TODO: LINK)** if you have problems with the implementation. |
||||
|
|
||||
|
## The Steps |
||||
|
|
||||
|
Our goal to enable database migrations in the `EntityFrameworkCore` project, remove the `EntityFrameworkCore.DbMigrations` project and revisit the code depending on that package. |
||||
|
|
||||
|
### 1) Add Microsoft.EntityFrameworkCore.Tools package to the EntityFrameworkCore project |
||||
|
|
||||
|
Add the following code into the `EntityFrameworkCore.csproj` file: |
||||
|
|
||||
|
````xml |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.*"> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> |
||||
|
<PrivateAssets>compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native</PrivateAssets> |
||||
|
</PackageReference> |
||||
|
</ItemGroup> |
||||
|
```` |
||||
|
|
||||
|
### 2) Create design time DbContext factory |
||||
|
|
||||
|
Create a class implementing `IDesignTimeDbContextFactory<T>` inside the `EntityFrameworkCore` project: |
||||
|
|
||||
|
````csharp |
||||
|
using System.IO; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.EntityFrameworkCore.Design; |
||||
|
using Microsoft.Extensions.Configuration; |
||||
|
|
||||
|
namespace UnifiedContextsDemo.EntityFrameworkCore |
||||
|
{ |
||||
|
public class UnifiedContextsDemoDbContextFactory : IDesignTimeDbContextFactory<UnifiedContextsDemoDbContext> |
||||
|
{ |
||||
|
public UnifiedContextsDemoDbContext CreateDbContext(string[] args) |
||||
|
{ |
||||
|
UnifiedContextsDemoEfCoreEntityExtensionMappings.Configure(); |
||||
|
|
||||
|
var configuration = BuildConfiguration(); |
||||
|
|
||||
|
var builder = new DbContextOptionsBuilder<UnifiedContextsDemoDbContext>() |
||||
|
.UseSqlServer(configuration.GetConnectionString("Default")); |
||||
|
|
||||
|
return new UnifiedContextsDemoDbContext(builder.Options); |
||||
|
} |
||||
|
|
||||
|
private static IConfigurationRoot BuildConfiguration() |
||||
|
{ |
||||
|
var builder = new ConfigurationBuilder() |
||||
|
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../UnifiedContextsDemo.DbMigrator/")) |
||||
|
.AddJsonFile("appsettings.json", optional: false); |
||||
|
|
||||
|
return builder.Build(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
I basically copied from the `EntityFrameworkCore.DbMigrations` project, renamed and uses the actual `DbContext` of the application. |
||||
|
|
||||
|
### 3) Create DB schema migrator |
||||
|
|
||||
|
Copy `EntityFrameworkCore...DbSchemaMigrator` (`...` standard for your project name) class to the `EntityFrameworkCore` project and change the code in the `MigrateAsync` method to use the actual `DbContext` of the application. In my case, the final class is shown below: |
||||
|
|
||||
|
````csharp |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using UnifiedContextsDemo.Data; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace UnifiedContextsDemo.EntityFrameworkCore |
||||
|
{ |
||||
|
public class EntityFrameworkCoreUnifiedContextsDemoDbSchemaMigrator |
||||
|
: IUnifiedContextsDemoDbSchemaMigrator, ITransientDependency |
||||
|
{ |
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
|
||||
|
public EntityFrameworkCoreUnifiedContextsDemoDbSchemaMigrator( |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_serviceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task MigrateAsync() |
||||
|
{ |
||||
|
/* We intentionally resolving the UnifiedContextsDemoMigrationsDbContext |
||||
|
* from IServiceProvider (instead of directly injecting it) |
||||
|
* to properly get the connection string of the current tenant in the |
||||
|
* current scope. |
||||
|
*/ |
||||
|
|
||||
|
await _serviceProvider |
||||
|
.GetRequiredService<UnifiedContextsDemoDbContext>() |
||||
|
.Database |
||||
|
.MigrateAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
### 4) Move module configurations |
||||
|
|
||||
|
The migrations `DbContext` typically contains code lines like `builder.ConfigureXXX()` for each module you are using. We can move these lines to our actual `DbContext` in the `EntityFrameworkCore` project. Also, remove the database mappings for the `AppUser` (we will remove this entity). Optionally, you may move the database mappings code for your own entities from `...DbContextModelCreatingExtensions` class in the `OnModelCreating` method of the actual `DbContext`, and remove the static extension class. |
||||
|
|
||||
|
For the example solution, the final `DbContext` class is shown below: |
||||
|
|
||||
|
````csharp |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using UnifiedContextsDemo.Users; |
||||
|
using Volo.Abp.AuditLogging.EntityFrameworkCore; |
||||
|
using Volo.Abp.BackgroundJobs.EntityFrameworkCore; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
using Volo.Abp.FeatureManagement.EntityFrameworkCore; |
||||
|
using Volo.Abp.Identity.EntityFrameworkCore; |
||||
|
using Volo.Abp.IdentityServer.EntityFrameworkCore; |
||||
|
using Volo.Abp.PermissionManagement.EntityFrameworkCore; |
||||
|
using Volo.Abp.SettingManagement.EntityFrameworkCore; |
||||
|
using Volo.Abp.TenantManagement.EntityFrameworkCore; |
||||
|
|
||||
|
namespace UnifiedContextsDemo.EntityFrameworkCore |
||||
|
{ |
||||
|
[ConnectionStringName("Default")] |
||||
|
public class UnifiedContextsDemoDbContext |
||||
|
: AbpDbContext<UnifiedContextsDemoDbContext> |
||||
|
{ |
||||
|
public DbSet<AppUser> Users { get; set; } |
||||
|
|
||||
|
/* Add DbSet properties for your Aggregate Roots / Entities here. |
||||
|
* Also map them inside UnifiedContextsDemoDbContextModelCreatingExtensions.ConfigureUnifiedContextsDemo |
||||
|
*/ |
||||
|
|
||||
|
public UnifiedContextsDemoDbContext( |
||||
|
DbContextOptions<UnifiedContextsDemoDbContext> options) |
||||
|
: base(options) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected override void OnModelCreating(ModelBuilder builder) |
||||
|
{ |
||||
|
base.OnModelCreating(builder); |
||||
|
|
||||
|
builder.ConfigurePermissionManagement(); |
||||
|
builder.ConfigureSettingManagement(); |
||||
|
builder.ConfigureBackgroundJobs(); |
||||
|
builder.ConfigureAuditLogging(); |
||||
|
builder.ConfigureIdentity(); |
||||
|
builder.ConfigureIdentityServer(); |
||||
|
builder.ConfigureFeatureManagement(); |
||||
|
builder.ConfigureTenantManagement(); |
||||
|
|
||||
|
/* Configure your own tables/entities inside here */ |
||||
|
|
||||
|
//builder.Entity<YourEntity>(b => |
||||
|
//{ |
||||
|
// b.ToTable(UnifiedContextsDemoConsts.DbTablePrefix + "YourEntities", UnifiedContextsDemoConsts.DbSchema); |
||||
|
// b.ConfigureByConvention(); //auto configure for the base class props |
||||
|
// //... |
||||
|
//}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
### 5) Remove `EntityFrameworkCore.DbMigrations` project from the solution |
||||
|
|
||||
|
Remove the `EntityFrameworkCore.DbMigrations` project from the solution and replace references given to that project by the `EntityFrameworkCore` project reference. |
||||
|
|
||||
|
Also, change usages of `...EntityFrameworkCoreDbMigrationsModule` to `...EntityFrameworkCoreModule` (`...` stands for your project name). |
||||
|
|
||||
|
In this example, I had to change references and usages in the `DbMigrator`, `Web` and `EntityFrameworkCore.Tests` projects. |
||||
|
|
||||
|
### 6) Remove AppUser Entity |
||||
|
|
||||
|
We need to remove the `AppUser` entity, because EF Core can't map two classes to single table without an inheritance relation. So, remove this class and all the usages. You can replace the usages with `IdentityUser` if you need to query users in your application code. |
||||
|
|
||||
|
TODO |
||||
|
After Width: | Height: | Size: 12 KiB |
Loading…
Reference in new issue