@ -4,7 +4,7 @@ In some cases, you may want to add some additional properties (and database fiel
## Extra Properties
[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these entities.
[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these objects.
Example:
@ -25,7 +25,35 @@ Extra properties are stored as a single `JSON` formatted string value in the dat
See the [entities document](Entities.md) for more about the extra properties system.
> It is possible to perform a **business logic** based on the value of an extra property. You can **override** a service method and get or set the value as shown above. Overriding services will be discussed below.
> It is possible to perform a **business logic** based on the value of an extra property. You can [override a service method](Customizing-Application-Modules-Overriding-Services.md), then get or set the value as shown above.
## Entity Extensions (EF Core)
As mentioned above, all extra properties of an entity are stored as a single JSON object in the database table. This is not so natural especially when you want to;
* Create **indexes** and **foreign keys** for an extra property.
* Write **SQL** or **LINQ** using the extra property (search table by the property value, for example).
* Creating your **own entity** maps to the same table, but defines an extra property as a **regular property** in the entity (see the [EF Core migration document](Entity-Framework-Core-Migrations.md) for more).
To overcome the difficulties described above, ABP Framework entity extension system for the Entity Framework Core that allows you to use the same extra properties API defined above, but store a desired property as a separate field in the database table.
Assume that you want to add a `SocialSecurityNumber` to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). You can use the `EntityExtensionManager` static class:
* You provide the `IdentityUser` as the entity name, `string` as the type of the new property, `SocialSecurityNumber` as the property name (also, the field name in the database table).
* You also need to provide an action that defines the database mapping properties using the [EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties).
> This code part must be executed before the related `DbContext` used. The [application startup template](Startup-Templates/Application.md) defines a static class named `YourProjectNameEntityExtensions`. You can define your extensions in this class to ensure that it is executed in the proper time. Otherwise, you should handle it yourself.
Once you define an entity extension, you then need to use the standard [Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration) and [Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database) commands of the EF Core to create a code first migration class and update your database.
You can then use the same extra properties system defined in the previous section to manipulate the property over the entity.
## Creating a New Entity Maps to the Same Database Table/Collection
@ -146,4 +174,5 @@ public class MyDistributedIdentityUserCreatedEventHandler :
## See Also
* [Migration System for the EF Core](Entity-Framework-Core-Migrations.md)
* [Customizing the Existing Modules](Customizing-Application-Modules-Guide.md)
@ -373,16 +373,19 @@ So, you can directly use the `ExtraProperties` property to use the dictionary A
The way to store this dictionary in the database depends on the database provider you're using.
* For [Entity Framework Core](Entity-Framework-Core.md), it is stored in a single `ExtraProperties` field as a `JSON` string. Serializing to `JSON` and deserializing from the `JSON` are automatically done by the ABP Framework using the [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) system of the EF Core.
* For [Entity Framework Core](Entity-Framework-Core.md), here are two type of configurations;
* By default, it is stored in a single `ExtraProperties` field as a `JSON` string (that means all extra properties stored in a single database table field). Serializing to `JSON` and deserializing from the `JSON` are automatically done by the ABP Framework using the [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) system of the EF Core.
* If you want, you can use the `EntityExtensionManager` to define a separate table field for a desired extra property. Properties those are not configured through the `EntityExtensionManager` will continue to use a single `JSON` field as described above. This feature is especially useful when you are using a pre-built [application module](Modules/Index.md) and want to [extend its entities](Customizing-Application-Modules-Extending-Entities.md). See the [EF Core integration document](Entity-Framework-Core.md) to learn how to use the `EntityExtensionManager`.
* For [MongoDB](MongoDB.md), it is stored as a **regular field**, since MongoDB naturally supports this kind of [extra elements](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) system.
### Discussion for the Extra Properties
Extra Properties system is especially useful if you are using a **re-usable module** that defines an entity inside and you want to get/set some data related to this entity in an easy way. You normally **don't need** to this system for your own entities, because it has the following drawbacks:
Extra Properties system is especially useful if you are using a **re-usable module** that defines an entity inside and you want to get/set some data related to this entity in an easy way.
* It is **not fully type safe**.
You normally **don't need** to this system for your own entities, because it has the following drawbacks:
* It is **not fully type safe** since it works with strings as property names.
* It is **not easy to [auto map](Object-To-Object-Mapping.md)** these properties from/to other objects.
* It **doesn't create fields** in the database table for EF Core, so it will not be easy to create indexes or search/order by this field in the database side.
@ -93,7 +93,7 @@ From the database point of view, there are three important projects those will b
This project has the `DbContext` class (`BookStoreDbContext` for this sample) of your application.
**Every module uses its own `DbContext` class** to access to the database. Likewise, your application has its own `DbContext`. You typically use this `DbContext` in your application code (in your custom [repositories](Repositories.md) if you follow the best practices). It is almost an empty `DbContext` since your application don't have any entities at the beginning, except the pre-defined `AppUser` entity:
**Every module uses its own `DbContext` class** to access to the database. Likewise, your application has its own `DbContext`. You typically use this `DbContext` in your application code (in your [repositories](Repositories.md) if you follow the best practices). It is almost an empty `DbContext` since your application don't have any entities at the beginning, except the pre-defined `AppUser` entity:
````csharp
[ConnectionStringName("Default")]
@ -117,15 +117,15 @@ public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
builder.Entity<AppUser>(b =>
{
//Sharing the same table "AbpUsers" with the IdentityUser
b.ToTable("AbpUsers");
//Configure base properties
//Sharing the same Users table with the IdentityUser
//Moved customization of the "AbpUsers" table to an extension method
b.ConfigureCustomUserProperties();
/* Configure mappings for your additional properties
* Also see the MyProjectNameEntityExtensions class
*/
});
/* Configure your own tables/entities inside the ConfigureBookStore method */
@ -188,12 +188,6 @@ public class BookStoreMigrationsDbContext : AbpDbContext<BookStoreMigrationsDbCo
builder.ConfigureFeatureManagement();
builder.ConfigureTenantManagement();
/* Configure customizations for entities from the modules included */
builder.Entity<IdentityUser>(b =>
{
b.ConfigureCustomUserProperties();
});
/* Configure your own tables/entities inside the ConfigureBookStore method */
builder.ConfigureBookStore();
}
@ -274,7 +268,7 @@ In this way, the mapping configuration of a module can be shared between `DbCont
You may want to **reuse a table** of a depended module in your application. In this case, you have two options:
1. You can **directly use the entity** defined by the module.
1. You can **directly use the entity** defined by the module (you can still [extend the entity](Customizing-Application-Modules-Extending-Entities.md) in some level).
2. You can **create a new entity** mapping to the same database table.
@ -395,69 +387,44 @@ We added the following lines:
````csharp
builder.Entity<AppRole>(b =>
{
b.ToTable("AbpRoles");
b.ToTable("AbpRoles");
b.ConfigureByConvention();
b.ConfigureCustomRoleProperties();
b.Property(x => x.Title).HasMaxLength(128);
});
````
* 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<TRole>(this EntityTypeBuilder<TRole> b)
* 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. This is because of EF Core migration system can not map two unrelated entity classes to the same database table.
You've configured the custom property for your `DbContext` that is used by your application on the runtime. We also need to configure the `MigrationsDbContext`.
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:
Instead of directly changing the `MigrationsDbContext`, we should use the entity extension system of the ABP Framework. Find the `YourProjectNameEntityExtensions` class in the `.EntityFrameworkCore` project of your solution (`BookStoreEntityExtensions` for this example) and change it as shown below:
> Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)`.
````csharp
builder.Entity<IdentityRole>(b =>
{
b.ConfigureCustomRoleProperties();
});
````
`EntityExtensionManager` is used to add properties to existing entities. Since `EntityExtensionManager` is static, we should call it once. `OneTimeRunner` is a simple utility class defined by the ABP Framework.
See the [EF Core integration documentation](Entity-Framework-Core.md) for more about the entity extension system.
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.
> We've repeated a similar database mapping code, like `HasMaxLength(128)`, in both classes.
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 and make sure that the `.Web` project is still the startup project):
@ -536,7 +503,7 @@ Instead of creating a new entity class to add a custom property, you can use the
###### Using the ExtraProperties
All entities derived from the `AggregateRoot ` class can store name-value pairs in their `ExtraProperties` property, which is a `Dictionary<string, object>` serialized to JSON in the database table. So, you can add values to this dictionary and query again without changing the entity.
All entities derived from the `AggregateRoot ` class can store name-value pairs in their `ExtraProperties` property (because they implement the `IHasExtraProperties` interface), which is a `Dictionary<string, object>` serialized to JSON in the database table. So, you can add values to this dictionary and query again without changing the entity.
For example, you can store query the title Property inside an `IdentityRole` instead of creating a new entity. Example:
@ -553,16 +520,13 @@ public class IdentityRoleExtendingService : ITransientDependency
public async Task<string> GetTitleAsync(Guid id)
{
var role = await _identityRoleRepository.GetAsync(id);
return role.GetProperty<string>("Title");
}
public async Task SetTitleAsync(Guid id, string newTitle)
{
var role = await _identityRoleRepository.GetAsync(id);
role.SetProperty("Title", newTitle);
await _identityRoleRepository.UpdateAsync(role);
}
}
@ -575,12 +539,20 @@ In this way, you can easily attach any type of value to an entity of a depended
* All the extra properties are stored as **a single JSON object** in the database. They are not stored as new table fields, as you may expect. Creating database table indexes and using SQL queries against these properties will be harder compared to simple table fields.
* Property names are strings, so they are **not type safe**. It is recommended to define constants for these kind of properties to prevent typo errors.
###### Using the Entity Extensions System
Entity extension system solves the main problem of the extra properties: It can store an extra property in a **standard table field** in the database.
All you need to do is to use the `EntityExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database.
###### Creating a New Table
Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table.
In this case, you don't deal with migration problems, however you need to deal with the problems of data duplication. When the duplicated value changes, you should reflect the same change in your table. You can use local or distributed [event bus](Event-Bus.md) to subscribe to the change events for the original entity. This is the recommended way of depending on a microservice's data from another microservice, especially if they have separate physical databases (you can search on the web on data sharing on a microservice design, it is a wide topic to cover here).
> See the "[extending entities](Customizing-Application-Modules-Extending-Entities.md)" guide for more details on extending entities, including data duplication and synchronization tips.
#### Discussion of an Alternative Scenario: Every Module Manages Its Own Migration Path
As mentioned before, `.EntityFrameworkCore.DbMigrations` merges all the database mappings of all the modules (plus your application's mappings) to create a unified migration path.
The [application startup template](Startup-Templates/Application.md) has been configured to use the [EF Core fluent configuration API](https://docs.microsoft.com/en-us/ef/core/modeling/) to map your entities to your database tables.
You can still use the **data annotation attributes** (like `[Required]`) on the properties of your entity while the ABP documentation generally follows the **fluent mapping API** approach. It is up to you.
ABP Framework has some **base entity classes** and **conventions** (see the [entities document](Entities.md)) and it provides some useful **extension methods** to configure the properties inherited from the base entity classes.
#### ConfigureByConvention Method
`ConfigureByConvention()` is the main extension method that **configures all the base properties** and conventions for your entities. So, it is a **best practice** to call this method for all your entities, in your fluent mapping code.
**Example**: Assume that you've a `Book` entity derived from `AggregateRoot<Guid>` base class:
````csharp
public class Book : AuditedAggregateRoot<Guid>
{
public string Name { get; set; }
}
````
You can override the `OnModelCreating` method in your `DbContext` and configure the mapping as shown below:
* Calling `b.ConfigureByConvention()` is important here to properly **configure the base properties**.
* You can configure the `Name` property here or you can use the **data annotation attributes** (see the [EF Core document](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)).
> While there are many extension methods to configure your base properties, `ConfigureByConvention()` internally calls them if necessary. So, it is enough to call it.
### Configure the Connection String Selection
If you have multiple databases in your application, you can configure the connection string name for your DbContext using the `[ConnectionStringName]` attribute. Example:
@ -225,7 +272,7 @@ public override async Task DeleteAsync(
}
````
### Access to the EF Core API
## Access to the EF Core API
In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContext()` or `GetDbSet()` extension methods. Example:
@ -251,9 +298,59 @@ public class BookService
> Important: You must reference to the `Volo.Abp.EntityFrameworkCore` package from the project you want to access to the DbContext. This breaks encapsulation, but this is what you want in that case.
### Advanced Topics
## Extra Properties & Entity Extension Manager
Extra Properties system allows you to set/get dynamic properties to entities those implement the `IHasExtraProperties` interface. It is especially useful when you want to add custom properties to the entities defined in an [application module](Modules/Index.md), when you use the module as package reference.
By default, all the extra properties of an entity are stored as a single `JSON` object in the database. Entity extension system allows you to to store desired extra properties in separate fields in the related database table.
#### Set Default Repository Classes
For more information about the extra properties & the entity extension system, see the following documents:
* [Customizing the Application Modules: Extending Entities](Customizing-Application-Modules-Extending-Entities.md)
* [Entities](Entities.md)
This section only explains the `EntityExtensionManager` and its usage.
### AddProperty Method
`AddProperty` method of the `EntityExtensionManager` allows you to define additional properties for an entity type.
**Example**: Add `Title` property (database field) to the `IdentityRole` entity:
If the related module has implemented this feature (by using the `ConfigureExtensions` explained below), then the new property is added to the model. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field.
>`AddProperty` method must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEntityExtensions` class that is safe to use this method inside.
### ConfigureExtensions
If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureExtensions` extension method in your entity mapping:
````csharp
builder.Entity<YourEntity>(b =>
{
b.ConfigureExtensions();
//...
});
````
If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` in this example), ABP Framework internally calls the `ConfigureExtensions` method. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention.
See the "*ConfigureByConvention Method*" section above for more information.
### GetPropertyNames
`EntityExtensionManager.GetPropertyNames` static method can be used the names of the extension properties defined for this entity. It is normally not needed by an application code, but used by the ABP Framework internally.
## Advanced Topics
### Set Default Repository Classes
Default generic repositories are implemented by `EfCoreRepository` class by default. You can create your own implementation and use it for all the default repository implementations.
#### Set Base DbContext Class or Interface for Default Repositories
### Set Base DbContext Class or Interface for Default Repositories
If your DbContext inherits from another DbContext or implements an interface, you can use that base class or interface as DbContext for default repositories. Example:
@ -331,7 +428,7 @@ public class BookRepository : EfCoreRepository<IBookStoreDbContext, Book, Guid>,
One advantage of using an interface for a DbContext is then it will be replaceable by another implementation.
#### Replace Other DbContextes
### Replace Other DbContextes
Once you properly define and use an interface for DbContext, then any other implementation can replace it using the `ReplaceDbContext` option:
In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime.