diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md index e17881b08d..395db99504 100644 --- a/docs/en/Entity-Framework-Core-Migrations.md +++ b/docs/en/Entity-Framework-Core-Migrations.md @@ -304,11 +304,181 @@ namespace Acme.BookStore This example injects the `IRepository` (default repository) which defines the standard repository methods and implements the `IQueryable` interface. -In addition, Identity module defines the `IIdentityUserRepository` (custom repository) that can also be injected and used by your application. `IIdentityUserRepository` provides additional custom methods for the `IdentityUser` entity while it does not implement the `IQueryable`. +> In addition, Identity module defines the `IIdentityUserRepository` (custom repository) that can also be injected and used by your application. `IIdentityUserRepository` provides additional custom methods for the `IdentityUser` entity while it does not implement the `IQueryable` interface. ###### Create a New Entity -TODO +Working with an entity of a module is easy if you want to use the entity as is. However, you may want to define your own entity class and map to the same database table in the following cases; + +* You want to add a new field to the table and map it to a property in the entity. You can't use the module's entity since it doesn't have the related property. +* You want to use a subset of the table fields. You don't want to access to all properties of the entity and hide the unrelated properties (from a security perspective or just by design). +* You don't want to directly depend on a module entity class. + +In any case, the progress is same. Assume that you want to create an entity, named `AppRole`, mapped to the same table of the `IdentityRole` entity of the [Identity module](Modules/Identity.md). + +Here, we will show the implementation, then **will discuss the limitations** (and reasons of the limitations) of this approach. + +First, create a new `AppRole` class in your `.Domain` project: + +````csharp +using System; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Acme.BookStore.Roles +{ + public class AppRole : AggregateRoot, IMultiTenant + { + // Properties shared with the IdentityRole class + + public Guid? TenantId { get; private set; } + public virtual string Name { get; protected internal set; } + + //Additional properties + + public string Title { get; set; } + + private AppRole() + { + + } + } +} +```` + +* It's inherited from [the `AggregateRoot` class](Entities.md) and implements [the `IMultiTenant` interface](Multi-Tenancy.md) because the `IdentityRole` also does the same. +* You can add any properties defined by the `IdentityRole` entity. This examples add only the `TenantId` and `Name` properties since we only need them here. +* You can add custom (additional) properties. This example adds the `Title` property. +* The constructor is provide, so it is not allowed to directly create a new `AppRole` entity. Creating a role is a responsibility of the Identity module. You can query roles, set/update your custom properties, but you should not create or delete a role in your code, as a best practice (while there is nothing restricts you). + +Now, it is time to define the EF Core mappings. Open the `DbContext` of your application (`BookStoreDbContext` in this sample) and add the following property: + +````csharp +public DbSet Roles { get; set; } +```` + +Then configure the mapping inside the `OnModelCreating` method (after calling the `base.OnModelCreating(builder)`): + +````csharp +protected override void OnModelCreating(ModelBuilder builder) +{ + base.OnModelCreating(builder); + + /* Configure the shared tables (with included modules) here */ + + //CONFIGURE THE AppRole ENTITY + builder.Entity(b => + { + b.ToTable("AbpRoles"); + + b.ConfigureByConvention(); + + b.ConfigureCustomRoleProperties(); + }); + + ... + + /* Configure your own tables/entities inside the ConfigureBookStore method */ + + builder.ConfigureBookStore(); +} +```` + +We added the following lines: + +````csharp +builder.Entity(b => +{ + b.ToTable("AbpRoles"); + + b.ConfigureByConvention(); + + b.ConfigureCustomRoleProperties(); +}); +```` + +* It maps to the same `AbpRoles` table shared with the `IdentityRole` entity. +* `ConfigureByConvention()` configures the standard/base properties (like `TenantId`) and recommended to always call it. + +`ConfigureCustomRoleProperties()` has not exists yet. Define it inside the `BookStoreDbContextModelCreatingExtensions` class (near to your `DbContext` in the `EntityFrameworkCore` project): + +````csharp +public static void ConfigureCustomRoleProperties(this EntityTypeBuilder b) + where TRole : class, IEntity +{ + b.Property(nameof(AppRole.Title)).HasMaxLength(128); +} +```` + +* This method only defines the custom properties of your entity. +* Unfortunately, we can not utilize the fully type safety here (by referencing the `AppRole` entity). The best we can do is to use the `Title` name as type safe. + +You've configured the custom property for your `DbContext` used by your application on the runtime. We also need to configure the `MigrationsDbContext`. Open the `MigrationsDbContext` (`BookStoreMigrationsDbContext` for this example) and change as shown below: + +````csharp +protected override void OnModelCreating(ModelBuilder builder) +{ + base.OnModelCreating(builder); + + /* Include modules to your migration db context */ + + ... + + /* Configure customizations for entities from the modules included */ + + //CONFIGURE THE CUSTOM ROLE PROPERTIES + builder.Entity(b => + { + b.ConfigureCustomRoleProperties(); + }); + + ... + + /* Configure your own tables/entities inside the ConfigureBookStore method */ + + builder.ConfigureBookStore(); +} +```` + +Only added the following lines: + +````csharp +builder.Entity(b => +{ + b.ConfigureCustomRoleProperties(); +}); +```` + +In this way, we re-used the extension method that is used to configure custom property mappings for the role. But, this time, did the same customization for the `IdentityRole` entity. + +Now, you can add a new EF Core database migration using the standard `Add-Migration` command in the Package Manager Console (remember to select `.EntityFrameworkCore.DbMigrations` as the Default Project in the PMC): + +![pmc-add-migration-role-title](images/pmc-add-migration-role-title.png) + +This command will create a new code first migration class as shown below: + +````csharp +public partial class Added_Title_To_Roles : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Title", + table: "AbpRoles", + maxLength: 128, + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Title", + table: "AbpRoles"); + } +} +```` + +All done! Just run the `Update-Database` command in the PMC or run the `.DbMigrator` project in your solution to apply changes to database. ##### Discussion of an Alternative Scenario: Every Module Manages Its Own Migration Path diff --git a/docs/en/images/pmc-add-migration-role-title.png b/docs/en/images/pmc-add-migration-role-title.png new file mode 100644 index 0000000000..69fc5b7bc5 Binary files /dev/null and b/docs/en/images/pmc-add-migration-role-title.png differ