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 09d5bbd923..2488ec9513 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -165,7 +165,7 @@ namespace Volo.Abp.EntityFrameworkCore FillExtraPropertiesForTrackedEntities(e); } - private static void FillExtraPropertiesForTrackedEntities(EntityTrackedEventArgs e) + protected virtual void FillExtraPropertiesForTrackedEntities(EntityTrackedEventArgs e) { var entityType = e.Entry.Metadata.ClrType; if (entityType == null) @@ -187,7 +187,18 @@ namespace Volo.Abp.EntityFrameworkCore foreach (var propertyName in propertyNames) { - entity.SetProperty(propertyName, e.Entry.CurrentValues[propertyName]); + /* 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. + * In this way, it prevents to delete old value in the JSON and + * updates the field on the next save! + */ + + var currentValue = e.Entry.CurrentValues[propertyName]; + if (currentValue != null) + { + entity.SetProperty(propertyName, currentValue); + } } } @@ -223,9 +234,9 @@ namespace Volo.Abp.EntityFrameworkCore AddDomainEvents(changeReport, entry.Entity); } - private void HandleExtraPropertiesOnSave(EntityEntry entry) + protected virtual void HandleExtraPropertiesOnSave(EntityEntry entry) { - if (entry.State == EntityState.Deleted) + if (entry.State.IsIn(EntityState.Deleted, EntityState.Unchanged)) { return; } 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 new file mode 100644 index 0000000000..f1aa105b9f --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs @@ -0,0 +1,14 @@ +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 index f6d9ccaaf5..d9f1ae9c31 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs @@ -1,60 +1,96 @@ 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 class EntityExtensionInfo - { - public Dictionary Properties { get; set; } - - public EntityExtensionInfo() - { - Properties = new Dictionary(); - } - } - - public class PropertyExtensionInfo - { - public List> Actions { get; } - - public Type PropertyType { get; } - - public PropertyExtensionInfo(Type propertyType) - { - PropertyType = propertyType; - Actions = new List>(); - } - } - public static class EntityExtensionManager { private static readonly Dictionary ExtensionInfos; - //TODO: Use PropertyBuilder instead - 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( - string name, - Action propertyBuildAction) + [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(typeof(TEntity), () => new EntityExtensionInfo()); + .GetOrAdd(entityType, () => new EntityExtensionInfo()); var propertyExtensionInfo = extensionInfo.Properties - .GetOrAdd(name, () => new PropertyExtensionInfo(typeof(TProperty))); + .GetOrAdd(propertyName, () => new PropertyExtensionInfo(propertyType)); - propertyExtensionInfo.Actions.Add(propertyBuildAction); + propertyExtensionInfo.Action = propertyBuildAction; } - public static void ConfigureProperties(EntityTypeBuilder entityTypeBuilder) + /// + /// 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 { - var entityExtensionInfo = ExtensionInfos.GetOrDefault(typeof(TEntity)); + 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; @@ -62,11 +98,21 @@ namespace Volo.Abp.EntityFrameworkCore.Extensions foreach (var propertyExtensionInfo in entityExtensionInfo.Properties) { - var property = entityTypeBuilder.Property(propertyExtensionInfo.Value.PropertyType, propertyExtensionInfo.Key); - foreach (var action in propertyExtensionInfo.Value.Actions) + var propertyName = propertyExtensionInfo.Key; + var propertyType = propertyExtensionInfo.Value.PropertyType; + + /* Prevent multiple calls to the entityTypeBuilder.Property(...) method */ + if (entityTypeBuilder.Metadata.FindProperty(propertyName) != null) { - action(property); + continue; } + + var property = entityTypeBuilder.Property( + propertyType, + propertyName + ); + + propertyExtensionInfo.Value.Action(property); } } 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 new file mode 100644 index 0000000000..df29bdd62d --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs @@ -0,0 +1,17 @@ +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/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs index 232a935dca..83e6d407a4 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 @@ -37,7 +37,7 @@ namespace Volo.Abp.EntityFrameworkCore modelBuilder.Entity(b => { - EntityExtensionManager.ConfigureProperties(b); + b.ConfigureExtensions(); b.OwnsMany(c => c.Districts, d => { 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 e8e7701b05..701aff4307 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 @@ -44,7 +44,7 @@ namespace Volo.Abp.TestApp.EntityFrameworkCore modelBuilder.Entity(b => { - EntityExtensionManager.ConfigureProperties(b); + b.ConfigureExtensions(); b.OwnsMany(c => c.Districts, d => { 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 6d5d182a51..fff3d98d11 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 @@ -28,6 +28,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.ConfigureFullAuditedAggregateRoot(); b.ConfigureAbpUser(); + b.ConfigureExtensions(); b.Property(u => u.NormalizedUserName).IsRequired().HasMaxLength(IdentityUserConsts.MaxNormalizedUserNameLength).HasColumnName(nameof(IdentityUser.NormalizedUserName)); b.Property(u => u.NormalizedEmail).IsRequired().HasMaxLength(IdentityUserConsts.MaxNormalizedEmailLength).HasColumnName(nameof(IdentityUser.NormalizedEmail)); @@ -36,9 +37,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.Property(u => u.TwoFactorEnabled).HasDefaultValue(false).HasColumnName(nameof(IdentityUser.TwoFactorEnabled)); b.Property(u => u.LockoutEnabled).HasDefaultValue(false).HasColumnName(nameof(IdentityUser.LockoutEnabled)); b.Property(u => u.AccessFailedCount).HasDefaultValue(0).HasColumnName(nameof(IdentityUser.AccessFailedCount)); - - EntityExtensions.ConfigureProperties(b); - + b.HasMany(u => u.Claims).WithOne().HasForeignKey(uc => uc.UserId).IsRequired(); b.HasMany(u => u.Logins).WithOne().HasForeignKey(ul => ul.UserId).IsRequired(); b.HasMany(u => u.Roles).WithOne().HasForeignKey(ur => ur.UserId).IsRequired();