Browse Source

Refactor ObjectExtensionManager & Update the documentation.

Removed EntityExtensionManager merged functionality to ObjectExtensionManager.
pull/3401/head
Halil İbrahim Kalkan 6 years ago
parent
commit
a3e9e0d533
  1. 4
      docs/en/Customizing-Application-Modules-Extending-Entities.md
  2. 2
      docs/en/Entities.md
  3. 15
      docs/en/Entity-Framework-Core-Migrations.md
  4. 41
      docs/en/Entity-Framework-Core.md
  5. 11
      framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs
  6. 35
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  7. 14
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs
  8. 133
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs
  9. 17
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs
  10. 32
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs
  11. 15
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
  12. 37
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs
  13. 75
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs
  14. 41
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs
  15. 31
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs
  16. 2
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs
  17. 4
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs
  18. 29
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs
  19. 13
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs
  20. 11
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs
  21. 3
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  22. 3
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  23. 1
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs

4
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<IdentityUser, string>(
ObjectExtensionManager.Instance.MapEfCoreProperty<IdentityUser, string>(
"SocialSecurityNumber",
b => { b.HasMaxLength(32); }
);

2
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

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

@ -409,18 +409,19 @@ public static class MyProjectNameEntityExtensions
{
OneTimeRunner.Run(() =>
{
EntityExtensionManager.AddProperty<IdentityRole, string>(
"Title",
b => { b.HasMaxLength(128); }
);
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"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

41
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<IdentityRole, string>(
"Title",
b => { b.HasMaxLength(128); }
);
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"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<YourEntity>(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

11
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<TDestination>().GetProperties();
return mappingExpression
.ForMember(
x => x.ExtraProperties,
@ -22,11 +21,15 @@ namespace AutoMapper
? new Dictionary<string, object>()
: new Dictionary<string, object>(extraProps);
foreach (var property in properties)
var objectExtension = ObjectExtensionManager.Instance.GetOrNull<TDestination>();
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];
}
}
}

35
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);
}
}

14
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs

@ -1,14 +0,0 @@
using System.Collections.Generic;
namespace Volo.Abp.EntityFrameworkCore.Extensions
{
public class EntityExtensionInfo
{
public Dictionary<string, PropertyExtensionInfo> Properties { get; set; }
public EntityExtensionInfo()
{
Properties = new Dictionary<string, PropertyExtensionInfo>();
}
}
}

133
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs

@ -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<Type, EntityExtensionInfo> ExtensionInfos;
static EntityExtensionManager()
{
ExtensionInfos = new Dictionary<Type, EntityExtensionInfo>();
}
/// <summary>
/// Adds an extension property for an entity.
/// If it is already added, replaces the <paramref name="propertyBuildAction"/>
/// by the given one!
/// </summary>
/// <typeparam name="TEntity">Type of the entity</typeparam>
/// <typeparam name="TProperty">Type of the new property</typeparam>
/// <param name="propertyName">Name of the property</param>
/// <param name="propertyBuildAction">An action to configure the database mapping for the new property</param>
public static void AddProperty<TEntity, TProperty>(
[NotNull]string propertyName,
[NotNull]Action<PropertyBuilder> propertyBuildAction)
{
AddProperty(
typeof(TEntity),
typeof(TProperty),
propertyName,
propertyBuildAction
);
}
/// <summary>
/// Adds an extension property for an entity.
/// If it is already added, replaces the <paramref name="propertyBuildAction"/>
/// by the given one!
/// </summary>
/// <param name="entityType">Type of the entity</param>
/// <param name="propertyType">Type of the new property</param>
/// <param name="propertyName">Name of the property</param>
/// <param name="propertyBuildAction">An action to configure the database mapping for the new property</param>
public static void AddProperty(
Type entityType,
Type propertyType,
[NotNull]string propertyName,
[NotNull]Action<PropertyBuilder> 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;
}
/// <summary>
/// Configures the entity mapping for the defined extensions.
/// </summary>
/// <typeparam name="TEntity">The entity tye</typeparam>
/// <param name="entityTypeBuilder">Entity type builder</param>
public static void ConfigureExtensions<TEntity>(
[NotNull] this EntityTypeBuilder<TEntity> entityTypeBuilder)
where TEntity : class, IHasExtraProperties
{
ConfigureExtensions(typeof(TEntity), entityTypeBuilder);
}
/// <summary>
/// Configures the entity mapping for the defined extensions.
/// </summary>
/// <param name="entityType">Type of the entity</param>
/// <param name="entityTypeBuilder">Entity type builder</param>
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<string>();
}
return entityExtensionInfo
.Properties
.Select(p => p.Key)
.ToArray();
}
}
}

17
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs

@ -1,17 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Volo.Abp.EntityFrameworkCore.Extensions
{
public class PropertyExtensionInfo
{
public Action<PropertyBuilder> Action { get; set; }
public Type PropertyType { get; }
public PropertyExtensionInfo(Type propertyType)
{
PropertyType = propertyType;
}
}
}

32
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<IHasExtraProperties>())
if (!b.Metadata.ClrType.IsAssignableTo<IHasExtraProperties>())
{
b.Property<Dictionary<string, object>>(nameof(IHasExtraProperties.ExtraProperties))
.HasColumnName(nameof(IHasExtraProperties.ExtraProperties))
.HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType))
.Metadata.SetValueComparer(new AbpDictionaryValueComparer<string, object>());
EntityExtensionManager.ConfigureExtensions(b.Metadata.ClrType, b);
return;
}
b.Property<Dictionary<string, object>>(nameof(IHasExtraProperties.ExtraProperties))
.HasColumnName(nameof(IHasExtraProperties.ExtraProperties))
.HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType))
.Metadata.SetValueComparer(new AbpDictionaryValueComparer<string, object>());
b.TryConfigureObjectExtensions();
}
public static void ConfigureObjectExtensions<T>(this EntityTypeBuilder<T> b)
where T : class
{
b.As<EntityTypeBuilder>().TryConfigureObjectExtensions();
}
public static void TryConfigureObjectExtensions(this EntityTypeBuilder b)
{
ObjectExtensionManager.Instance.ConfigureEfCoreEntity(b);
}
public static void ConfigureSoftDelete<T>(this EntityTypeBuilder<T> b)
@ -286,7 +299,6 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling
b.As<EntityTypeBuilder>().TryConfigureConcurrencyStamp();
}
//TODO: Add other interfaces (IAuditedObject<TUser>...)
}
}

15
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);
}
}
}
}

37
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<TDbField>(
this ObjectExtensionInfo objectExtensionInfo,
string propertyName,
Action<PropertyBuilder> propertyBuildAction)
{
return objectExtensionInfo.MapEfCoreProperty(
typeof(TDbField),
propertyName,
propertyBuildAction
);
}
public static ObjectExtensionPropertyInfo MapEfCoreProperty(
this ObjectExtensionInfo objectExtensionInfo,
Type dbFieldType,
string propertyName,
Action<PropertyBuilder> propertyBuildAction)
{
return objectExtensionInfo.AddOrUpdateProperty(
propertyName,
options =>
{
options.MapEfCore(
dbFieldType,
propertyBuildAction
);
});
}
}
}

75
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<TObject, TDbField>(
this ObjectExtensionManager objectExtensionManager,
string propertyName,
Action<PropertyBuilder> propertyBuildAction)
{
return objectExtensionManager.MapEfCoreProperty(
typeof(TObject),
typeof(TDbField),
propertyName,
propertyBuildAction
);
}
public static ObjectExtensionInfo MapEfCoreProperty(
this ObjectExtensionManager objectExtensionManager,
Type objectType,
Type dbFieldType,
string propertyName,
Action<PropertyBuilder> 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);
}
}
}
}

41
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<PropertyBuilder> 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);
}
}
}

31
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<PropertyBuilder> PropertyBuildAction { get; set; }
public ObjectExtensionPropertyInfoEfCoreMappingOptions(
[NotNull] Type fieldType,
[NotNull] ObjectExtensionPropertyInfo extensionProperty,
[CanBeNull] Action<PropertyBuilder> propertyBuildAction = null)
{
FieldType = Check.NotNull(fieldType, nameof(fieldType));
ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty));
PropertyBuildAction = propertyBuildAction;
}
}
}

2
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<TSource>().GetProperties();
var extensionPropertyInfos = ObjectExtensionManager.Instance.AddOrUpdate<TSource>().GetProperties();
foreach (var extensionPropertyInfo in extensionPropertyInfos)
{

4
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs

@ -19,13 +19,13 @@ namespace Volo.Abp.ObjectExtending
Configuration = new Dictionary<object, object>();
}
public ObjectExtensionPropertyInfo AddProperty(
public ObjectExtensionPropertyInfo AddOrUpdateProperty(
string propertyName,
Action<ObjectExtensionPropertyInfo> configureAction = null)
{
var propertyInfo = Properties.GetOrAdd(
propertyName,
() => new ObjectExtensionPropertyInfo(propertyName)
() => new ObjectExtensionPropertyInfo(this, propertyName)
);
configureAction?.Invoke(propertyInfo);

29
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<Type, ObjectExtensionInfo> ObjectsExtensions { get; }
protected Dictionary<Type, ObjectExtensionInfo> ObjectsExtensions { get; }
private ObjectExtensionManager()
protected ObjectExtensionManager()
{
ObjectsExtensions = new Dictionary<Type, ObjectExtensionInfo>();
}
public ObjectExtensionInfo For<TObject>(
public virtual ObjectExtensionInfo AddOrUpdate<TObject>(
Action<ObjectExtensionInfo> configureAction = null)
{
return AddOrUpdate(typeof(TObject), configureAction);
}
public virtual ObjectExtensionInfo AddOrUpdate(
Type type,
Action<ObjectExtensionInfo> 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<TObject>()
{
return GetOrNull(typeof(TObject));
}
public virtual ObjectExtensionInfo GetOrNull(Type type)
{
return ObjectsExtensions.GetOrDefault(type);
}
}
}

13
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<ValidationAttribute> ValidationAttributes { get; }
[NotNull]
public Dictionary<object, object> 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<ValidationAttribute>();
Configuration = new Dictionary<object, object>();
}

11
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<City, string>(
"PhoneCode",
p => p.HasMaxLength(8)
);
ObjectExtensionManager.Instance
.MapEfCoreProperty<City, string>(
"PhoneCode",
p => p.HasMaxLength(8)
);
});
}
}

3
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<City>(b =>
{
//b.ConfigureExtensions();
b.OwnsMany(c => c.Districts, d =>
{
d.WithOwner().HasForeignKey(x => x.CityId);

3
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<City>(b =>
{
//b.ConfigureExtensions();
b.OwnsMany(c => c.Districts, d =>
{
d.WithOwner().HasForeignKey(x => x.CityId);

1
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;

Loading…
Cancel
Save