diff --git a/docs/en/Customizing-Application-Modules-Extending-Entities.md b/docs/en/Customizing-Application-Modules-Extending-Entities.md index be8bda6068..376e7a1517 100644 --- a/docs/en/Customizing-Application-Modules-Extending-Entities.md +++ b/docs/en/Customizing-Application-Modules-Extending-Entities.md @@ -37,10 +37,10 @@ As mentioned above, all extra properties of an entity are stored as a single JSO 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: +Assume that you want to add a `SocialSecurityNumber` to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). You can use the `ObjectExtensionManager`: ````csharp -EntityExtensionManager.AddProperty( +ObjectExtensionManager.Instance.MapEfCoreProperty( "SocialSecurityNumber", b => { b.HasMaxLength(32); } ); diff --git a/docs/en/Entities.md b/docs/en/Entities.md index fee1a3618c..8a234b94a5 100644 --- a/docs/en/Entities.md +++ b/docs/en/Entities.md @@ -375,7 +375,7 @@ The way to store this dictionary in the database depends on the database provide * 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`. + * If you want, you can use the `ObjectExtensionManager` to define a separate table field for a desired extra property. Properties those are not configured through the `ObjectExtensionManager` 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 `ObjectExtensionManager`. * 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 diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md index 5d2fff011c..fbe0f59d70 100644 --- a/docs/en/Entity-Framework-Core-Migrations.md +++ b/docs/en/Entity-Framework-Core-Migrations.md @@ -409,18 +409,19 @@ public static class MyProjectNameEntityExtensions { OneTimeRunner.Run(() => { - EntityExtensionManager.AddProperty( - "Title", - b => { b.HasMaxLength(128); } - ); + ObjectExtensionManager.Instance + .MapEfCoreProperty( + "Title", + builder => { builder.HasMaxLength(64); } + ); }); } } ```` -> Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)`. +> Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)` or use a constant string. -`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. +`ObjectExtensionManager` is used to add properties to existing entities. Since `ObjectExtensionManager.Instance` is a static instance (singleton), 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. @@ -543,7 +544,7 @@ In this way, you can easily attach any type of value to an entity of a depended 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. +All you need to do is to use the `ObjectExtensionManager` 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 diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md index d908ce03f5..66f9d3c2f9 100644 --- a/docs/en/Entity-Framework-Core.md +++ b/docs/en/Entity-Framework-Core.md @@ -298,56 +298,57 @@ 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. -## Extra Properties & Entity Extension Manager +## Extra Properties & Object 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. +By default, all the extra properties of an entity are stored as a single `JSON` object in the database. -For more information about the extra properties & the entity extension system, see the following documents: +Entity extension system allows you to to store desired extra properties in separate fields in the related database table. 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. +This section only explains the EF Core related usage of the `ObjectExtensionManager`. -### AddProperty Method +### ObjectExtensionManager.Instance -`AddProperty` method of the `EntityExtensionManager` allows you to define additional properties for an entity type. +`ObjectExtensionManager` implements the singleton pattern, so you need to use the static `ObjectExtensionManager.Instance` to perform all the operations. + +### MapEfCoreProperty + +`MapEfCoreProperty` is a shortcut extension method to define an extension property for an entity and map to the database. **Example**: Add `Title` property (database field) to the `IdentityRole` entity: ````csharp -EntityExtensionManager.AddProperty( - "Title", - b => { b.HasMaxLength(128); } -); +ObjectExtensionManager.Instance + .MapEfCoreProperty( + "Title", + builder => { builder.HasMaxLength(64); } + ); ```` -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. +If the related module has implemented this feature (by using the `ConfigureEfCoreEntity` 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. +>`MapEfCoreProperty` 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 +### ConfigureEfCoreEntity -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: +If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureEfCoreEntity` extension method in your entity mapping. However, there is a shortcut extension method `ConfigureObjectExtensions` that can be used while configuring the entity mapping: ````csharp builder.Entity(b => { - b.ConfigureExtensions(); + b.ConfigureObjectExtensions(); //... }); ```` -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. +> If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` for this example), ABP Framework internally calls the `ConfigureObjectExtensions` 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 diff --git a/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs b/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs index dc5741e07f..91daadf350 100644 --- a/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs +++ b/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs @@ -11,7 +11,6 @@ namespace AutoMapper where TDestination : IHasExtraProperties where TSource : IHasExtraProperties { - var properties = ObjectExtensionManager.Instance.For().GetProperties(); return mappingExpression .ForMember( x => x.ExtraProperties, @@ -22,11 +21,15 @@ namespace AutoMapper ? new Dictionary() : new Dictionary(extraProps); - foreach (var property in properties) + var objectExtension = ObjectExtensionManager.Instance.GetOrNull(); + if (objectExtension != null) { - if (source.ExtraProperties.ContainsKey(property.Name)) + foreach (var property in objectExtension.GetProperties()) { - result[property.Name] = source.ExtraProperties[property.Name]; + if (source.ExtraProperties.ContainsKey(property.Name)) + { + result[property.Name] = source.ExtraProperties[property.Name]; + } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 2488ec9513..3ba7092395 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -18,11 +18,11 @@ using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities.Events; using Volo.Abp.Domain.Repositories; using Volo.Abp.EntityFrameworkCore.EntityHistory; -using Volo.Abp.EntityFrameworkCore.Extensions; using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.ValueConverters; using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; +using Volo.Abp.ObjectExtending; using Volo.Abp.Reflection; using Volo.Abp.Timing; using Volo.Abp.Uow; @@ -183,10 +183,18 @@ namespace Volo.Abp.EntityFrameworkCore return; } - var propertyNames = EntityExtensionManager.GetPropertyNames(entityType); + var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType); + if (objectExtension == null) + { + return; + } - foreach (var propertyName in propertyNames) + foreach (var property in objectExtension.GetProperties()) { + if (!property.IsMappedToFieldForEfCore()) + { + continue; + } /* Checking "currentValue != null" has a good advantage: * Assume that you we already using a named extra property, * then decided to create a field (entity extension) for it. @@ -194,10 +202,10 @@ namespace Volo.Abp.EntityFrameworkCore * updates the field on the next save! */ - var currentValue = e.Entry.CurrentValues[propertyName]; + var currentValue = e.Entry.CurrentValues[property.Name]; if (currentValue != null) { - entity.SetProperty(propertyName, currentValue); + entity.SetProperty(property.Name, currentValue); } } } @@ -251,12 +259,21 @@ namespace Volo.Abp.EntityFrameworkCore { return; } - - var propertyNames = EntityExtensionManager.GetPropertyNames(entityType); - foreach (var propertyName in propertyNames) + var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType); + if (objectExtension == null) + { + return; + } + + foreach (var property in objectExtension.GetProperties()) { - entry.Property(propertyName).CurrentValue = entity.GetProperty(propertyName); + if (!entity.HasProperty(property.Name)) + { + continue; + } + + entry.Property(property.Name).CurrentValue = entity.GetProperty(property.Name); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs deleted file mode 100644 index f1aa105b9f..0000000000 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.Abp.EntityFrameworkCore.Extensions -{ - public class EntityExtensionInfo - { - public Dictionary Properties { get; set; } - - public EntityExtensionInfo() - { - Properties = new Dictionary(); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs deleted file mode 100644 index d9f1ae9c31..0000000000 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Volo.Abp.Data; - -namespace Volo.Abp.EntityFrameworkCore.Extensions -{ - public static class EntityExtensionManager - { - private static readonly Dictionary ExtensionInfos; - - static EntityExtensionManager() - { - ExtensionInfos = new Dictionary(); - } - - /// - /// Adds an extension property for an entity. - /// If it is already added, replaces the - /// by the given one! - /// - /// Type of the entity - /// Type of the new property - /// Name of the property - /// An action to configure the database mapping for the new property - public static void AddProperty( - [NotNull]string propertyName, - [NotNull]Action propertyBuildAction) - { - AddProperty( - typeof(TEntity), - typeof(TProperty), - propertyName, - propertyBuildAction - ); - } - - /// - /// Adds an extension property for an entity. - /// If it is already added, replaces the - /// by the given one! - /// - /// Type of the entity - /// Type of the new property - /// Name of the property - /// An action to configure the database mapping for the new property - public static void AddProperty( - Type entityType, - Type propertyType, - [NotNull]string propertyName, - [NotNull]Action propertyBuildAction) - { - Check.NotNull(entityType, nameof(entityType)); - Check.NotNull(propertyType, nameof(propertyType)); - Check.NotNullOrWhiteSpace(propertyName, nameof(propertyName)); - Check.NotNull(propertyBuildAction, nameof(propertyBuildAction)); - - var extensionInfo = ExtensionInfos - .GetOrAdd(entityType, () => new EntityExtensionInfo()); - - var propertyExtensionInfo = extensionInfo.Properties - .GetOrAdd(propertyName, () => new PropertyExtensionInfo(propertyType)); - - propertyExtensionInfo.Action = propertyBuildAction; - } - - /// - /// Configures the entity mapping for the defined extensions. - /// - /// The entity tye - /// Entity type builder - public static void ConfigureExtensions( - [NotNull] this EntityTypeBuilder entityTypeBuilder) - where TEntity : class, IHasExtraProperties - { - ConfigureExtensions(typeof(TEntity), entityTypeBuilder); - } - - /// - /// Configures the entity mapping for the defined extensions. - /// - /// Type of the entity - /// Entity type builder - public static void ConfigureExtensions( - [NotNull] Type entityType, - [NotNull] EntityTypeBuilder entityTypeBuilder) - { - Check.NotNull(entityType, nameof(entityType)); - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - - var entityExtensionInfo = ExtensionInfos.GetOrDefault(entityType); - if (entityExtensionInfo == null) - { - return; - } - - foreach (var propertyExtensionInfo in entityExtensionInfo.Properties) - { - var propertyName = propertyExtensionInfo.Key; - var propertyType = propertyExtensionInfo.Value.PropertyType; - - /* Prevent multiple calls to the entityTypeBuilder.Property(...) method */ - if (entityTypeBuilder.Metadata.FindProperty(propertyName) != null) - { - continue; - } - - var property = entityTypeBuilder.Property( - propertyType, - propertyName - ); - - propertyExtensionInfo.Value.Action(property); - } - } - - public static string[] GetPropertyNames(Type entityType) - { - var entityExtensionInfo = ExtensionInfos.GetOrDefault(entityType); - if (entityExtensionInfo == null) - { - return Array.Empty(); - } - - return entityExtensionInfo - .Properties - .Select(p => p.Key) - .ToArray(); - } - } -} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs deleted file mode 100644 index df29bdd62d..0000000000 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace Volo.Abp.EntityFrameworkCore.Extensions -{ - public class PropertyExtensionInfo - { - public Action Action { get; set; } - - public Type PropertyType { get; } - - public PropertyExtensionInfo(Type propertyType) - { - PropertyType = propertyType; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs index eca8831e35..b64866f8be 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs @@ -5,10 +5,10 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Volo.Abp.Auditing; using Volo.Abp.Data; using Volo.Abp.Domain.Entities; -using Volo.Abp.EntityFrameworkCore.Extensions; using Volo.Abp.EntityFrameworkCore.ValueComparers; using Volo.Abp.EntityFrameworkCore.ValueConverters; using Volo.Abp.MultiTenancy; +using Volo.Abp.ObjectExtending; namespace Volo.Abp.EntityFrameworkCore.Modeling { @@ -18,6 +18,7 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling { b.TryConfigureConcurrencyStamp(); b.TryConfigureExtraProperties(); + b.TryConfigureObjectExtensions(); b.TryConfigureMayHaveCreator(); b.TryConfigureMustHaveCreator(); b.TryConfigureSoftDelete(); @@ -54,16 +55,28 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling public static void TryConfigureExtraProperties(this EntityTypeBuilder b) { - //TODO: Max length? - if (b.Metadata.ClrType.IsAssignableTo()) + if (!b.Metadata.ClrType.IsAssignableTo()) { - b.Property>(nameof(IHasExtraProperties.ExtraProperties)) - .HasColumnName(nameof(IHasExtraProperties.ExtraProperties)) - .HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType)) - .Metadata.SetValueComparer(new AbpDictionaryValueComparer()); - - EntityExtensionManager.ConfigureExtensions(b.Metadata.ClrType, b); + return; } + + b.Property>(nameof(IHasExtraProperties.ExtraProperties)) + .HasColumnName(nameof(IHasExtraProperties.ExtraProperties)) + .HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType)) + .Metadata.SetValueComparer(new AbpDictionaryValueComparer()); + + b.TryConfigureObjectExtensions(); + } + + public static void ConfigureObjectExtensions(this EntityTypeBuilder b) + where T : class + { + b.As().TryConfigureObjectExtensions(); + } + + public static void TryConfigureObjectExtensions(this EntityTypeBuilder b) + { + ObjectExtensionManager.Instance.ConfigureEfCoreEntity(b); } public static void ConfigureSoftDelete(this EntityTypeBuilder b) @@ -286,7 +299,6 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling b.As().TryConfigureConcurrencyStamp(); } - //TODO: Add other interfaces (IAuditedObject...) } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs index fb1abf0e24..ddcdb942ba 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Newtonsoft.Json; -using Volo.Abp.EntityFrameworkCore.Extensions; +using Volo.Abp.ObjectExtending; namespace Volo.Abp.EntityFrameworkCore.ValueConverters { @@ -22,11 +22,16 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters if (entityType != null) { - var propertyNames = EntityExtensionManager.GetPropertyNames(entityType); - - foreach (var propertyName in propertyNames) + var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType); + if (objectExtension != null) { - copyDictionary.Remove(propertyName); + foreach (var property in objectExtension.GetProperties()) + { + if (property.IsMappedToFieldForEfCore()) + { + copyDictionary.Remove(property.Name); + } + } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs new file mode 100644 index 0000000000..783edf8b8b --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Volo.Abp.ObjectExtending +{ + public static class EfCoreObjectExtensionInfoExtensions + { + public static ObjectExtensionPropertyInfo MapEfCoreProperty( + this ObjectExtensionInfo objectExtensionInfo, + string propertyName, + Action propertyBuildAction) + { + return objectExtensionInfo.MapEfCoreProperty( + typeof(TDbField), + propertyName, + propertyBuildAction + ); + } + + public static ObjectExtensionPropertyInfo MapEfCoreProperty( + this ObjectExtensionInfo objectExtensionInfo, + Type dbFieldType, + string propertyName, + Action propertyBuildAction) + { + return objectExtensionInfo.AddOrUpdateProperty( + propertyName, + options => + { + options.MapEfCore( + dbFieldType, + propertyBuildAction + ); + }); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs new file mode 100644 index 0000000000..9261bdc3e8 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs @@ -0,0 +1,75 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Volo.Abp.ObjectExtending +{ + public static class EfCoreObjectExtensionManagerExtensions + { + public static ObjectExtensionInfo MapEfCoreProperty( + this ObjectExtensionManager objectExtensionManager, + string propertyName, + Action propertyBuildAction) + { + return objectExtensionManager.MapEfCoreProperty( + typeof(TObject), + typeof(TDbField), + propertyName, + propertyBuildAction + ); + } + + public static ObjectExtensionInfo MapEfCoreProperty( + this ObjectExtensionManager objectExtensionManager, + Type objectType, + Type dbFieldType, + string propertyName, + Action propertyBuildAction) + { + return objectExtensionManager.AddOrUpdate( + objectType, + objectOptions => + { + objectOptions.AddOrUpdateProperty( + propertyName, + propertyOptions => + { + propertyOptions.MapEfCore( + dbFieldType, + propertyBuildAction + ); + } + ); + }); + } + + public static void ConfigureEfCoreEntity( + this ObjectExtensionManager objectExtensionManager, + EntityTypeBuilder b) + { + var objectExtension = objectExtensionManager.GetOrNull(b.Metadata.ClrType); + if (objectExtension == null) + { + return; + } + + foreach (var property in objectExtension.GetProperties()) + { + var efCoreMapping = property.GetEfCoreMappingOrNull(); + if (efCoreMapping == null) + { + continue; + } + + /* Prevent multiple calls to the entityTypeBuilder.Property(...) method */ + if (b.Metadata.FindProperty(property.Name) != null) + { + continue; + } + + var propertyBuilder = b.Property(efCoreMapping.FieldType, property.Name); + + efCoreMapping.PropertyBuildAction?.Invoke(propertyBuilder); + } + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs new file mode 100644 index 0000000000..9f97d0d7f4 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Volo.Abp.ObjectExtending +{ + public static class EfCoreObjectExtensionPropertyInfoExtensions + { + public const string EfCorePropertyConfigurationName = "EfCoreMapping"; + + public static ObjectExtensionPropertyInfo MapEfCore( + this ObjectExtensionPropertyInfo propertyExtension, + Type dbFieldType, + Action propertyBuildAction) + { + var options = new ObjectExtensionPropertyInfoEfCoreMappingOptions( + dbFieldType, + propertyExtension, + propertyBuildAction + ); + + propertyExtension.Configuration[EfCorePropertyConfigurationName] = options; + + return propertyExtension; + } + + [CanBeNull] + public static ObjectExtensionPropertyInfoEfCoreMappingOptions GetEfCoreMappingOrNull( + this ObjectExtensionPropertyInfo propertyExtension) + { + return propertyExtension.Configuration.GetOrDefault(EfCorePropertyConfigurationName) + as ObjectExtensionPropertyInfoEfCoreMappingOptions; + } + + public static bool IsMappedToFieldForEfCore(this ObjectExtensionPropertyInfo propertyExtension) + { + return propertyExtension.Configuration.ContainsKey(EfCorePropertyConfigurationName); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs new file mode 100644 index 0000000000..20dd1cef0a --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs @@ -0,0 +1,31 @@ +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Volo.Abp.ObjectExtending +{ + public class ObjectExtensionPropertyInfoEfCoreMappingOptions + { + [NotNull] + public ObjectExtensionPropertyInfo ExtensionProperty { get; } + + [NotNull] + public ObjectExtensionInfo ObjectExtension => ExtensionProperty.ObjectExtension; + + [NotNull] + public Type FieldType { get; } + + [CanBeNull] + public Action PropertyBuildAction { get; set; } + + public ObjectExtensionPropertyInfoEfCoreMappingOptions( + [NotNull] Type fieldType, + [NotNull] ObjectExtensionPropertyInfo extensionProperty, + [CanBeNull] Action propertyBuildAction = null) + { + FieldType = Check.NotNull(fieldType, nameof(fieldType)); + ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty)); + PropertyBuildAction = propertyBuildAction; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs index 86e288c932..3f415172e2 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.ObjectExtending where TSource : IHasExtraProperties where TDestination : IHasExtraProperties { - var extensionPropertyInfos = ObjectExtensionManager.Instance.For().GetProperties(); + var extensionPropertyInfos = ObjectExtensionManager.Instance.AddOrUpdate().GetProperties(); foreach (var extensionPropertyInfo in extensionPropertyInfos) { diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs index 04698c6b69..ffe1c8b03c 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs @@ -19,13 +19,13 @@ namespace Volo.Abp.ObjectExtending Configuration = new Dictionary(); } - public ObjectExtensionPropertyInfo AddProperty( + public ObjectExtensionPropertyInfo AddOrUpdateProperty( string propertyName, Action configureAction = null) { var propertyInfo = Properties.GetOrAdd( propertyName, - () => new ObjectExtensionPropertyInfo(propertyName) + () => new ObjectExtensionPropertyInfo(this, propertyName) ); configureAction?.Invoke(propertyInfo); diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs index ad820522f2..d6a42e82c7 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs @@ -5,26 +5,43 @@ namespace Volo.Abp.ObjectExtending { public class ObjectExtensionManager { - public static ObjectExtensionManager Instance { get; } = new ObjectExtensionManager(); + public static ObjectExtensionManager Instance { get; set; } = new ObjectExtensionManager(); - private Dictionary ObjectsExtensions { get; } + protected Dictionary ObjectsExtensions { get; } - private ObjectExtensionManager() + protected ObjectExtensionManager() { ObjectsExtensions = new Dictionary(); } - public ObjectExtensionInfo For( + public virtual ObjectExtensionInfo AddOrUpdate( + Action configureAction = null) + { + return AddOrUpdate(typeof(TObject), configureAction); + } + + public virtual ObjectExtensionInfo AddOrUpdate( + Type type, Action configureAction = null) { var extensionInfo = ObjectsExtensions.GetOrAdd( - typeof(TObject), - () => new ObjectExtensionInfo(typeof(TObject)) + type, + () => new ObjectExtensionInfo(type) ); configureAction?.Invoke(extensionInfo); return extensionInfo; } + + public virtual ObjectExtensionInfo GetOrNull() + { + return GetOrNull(typeof(TObject)); + } + + public virtual ObjectExtensionInfo GetOrNull(Type type) + { + return ObjectsExtensions.GetOrDefault(type); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs index 981bfd3040..baac9962fa 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs @@ -1,19 +1,28 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; namespace Volo.Abp.ObjectExtending { public class ObjectExtensionPropertyInfo { + [NotNull] + public ObjectExtensionInfo ObjectExtension { get; } + + [NotNull] public string Name { get; } + [NotNull] public List ValidationAttributes { get; } + [NotNull] public Dictionary Configuration { get; } - public ObjectExtensionPropertyInfo(string name) + public ObjectExtensionPropertyInfo(ObjectExtensionInfo objectExtension, string name) { - Name = name; + ObjectExtension = Check.NotNull(objectExtension, nameof(objectExtension)); + Name = Check.NotNull(name, nameof(name)); + ValidationAttributes = new List(); Configuration = new Dictionary(); } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs index 2ae6888e7f..3d415d79e6 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs @@ -1,4 +1,4 @@ -using Volo.Abp.EntityFrameworkCore.Extensions; +using Volo.Abp.ObjectExtending; using Volo.Abp.TestApp.Domain; using Volo.Abp.Threading; @@ -12,10 +12,11 @@ namespace Volo.Abp.EntityFrameworkCore.Domain { OneTimeRunner.Run(() => { - EntityExtensionManager.AddProperty( - "PhoneCode", - p => p.HasMaxLength(8) - ); + ObjectExtensionManager.Instance + .MapEfCoreProperty( + "PhoneCode", + p => p.HasMaxLength(8) + ); }); } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs index 6293e21716..da66392a18 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore.Extensions; using Volo.Abp.EntityFrameworkCore.TestApp.SecondContext; using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext; using Volo.Abp.TestApp.Domain; @@ -37,8 +36,6 @@ namespace Volo.Abp.EntityFrameworkCore modelBuilder.Entity(b => { - //b.ConfigureExtensions(); - b.OwnsMany(c => c.Districts, d => { d.WithOwner().HasForeignKey(x => x.CityId); diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs index 3bc1678764..289364411d 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs @@ -1,6 +1,5 @@ using Microsoft.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore.Extensions; using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext; using Volo.Abp.TestApp.Domain; @@ -44,8 +43,6 @@ namespace Volo.Abp.TestApp.EntityFrameworkCore modelBuilder.Entity(b => { - //b.ConfigureExtensions(); - b.OwnsMany(c => c.Districts, d => { d.WithOwner().HasForeignKey(x => x.CityId); diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs index c5176755d1..05b99e767e 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs @@ -1,7 +1,6 @@ using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore.Extensions; using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.Users.EntityFrameworkCore;