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 6cfbe3d01c..5933997cdc 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -18,6 +18,7 @@ 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; @@ -155,6 +156,39 @@ namespace Volo.Abp.EntityFrameworkCore ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; + + ChangeTracker.Tracked += ChangeTracker_Tracked; + } + + protected virtual void ChangeTracker_Tracked(object sender, EntityTrackedEventArgs e) + { + FillExtraPropertiesForTrackedEntities(e); + } + + private static void FillExtraPropertiesForTrackedEntities(EntityTrackedEventArgs e) + { + var entityType = e.Entry.Metadata.ClrType; + if (entityType == null) + { + return; + } + + if (!(e.Entry.Entity is IHasExtraProperties entity)) + { + return; + } + + if (!e.FromQuery) + { + return; + } + + var propertyNames = EntityExtensions.GetPropertyNames(entityType); + + foreach (var propertyName in propertyNames) + { + entity.SetProperty(propertyName, e.Entry.CurrentValues[propertyName]); + } } protected virtual EntityChangeReport ApplyAbpConcepts() @@ -184,9 +218,37 @@ namespace Volo.Abp.EntityFrameworkCore break; } + HandleExtraPropertiesOnSave(entry); + AddDomainEvents(changeReport, entry.Entity); } + private void HandleExtraPropertiesOnSave(EntityEntry entry) + { + if (entry.State == EntityState.Deleted) + { + return; + } + + var entityType = entry.Metadata.ClrType; + if (entityType == null) + { + return; + } + + if (!(entry.Entity is IHasExtraProperties entity)) + { + return; + } + + var propertyNames = EntityExtensions.GetPropertyNames(entityType); + + foreach (var propertyName in propertyNames) + { + entry.Property(propertyName).CurrentValue = entity.GetProperty(propertyName); + } + } + protected virtual void ApplyAbpConceptsForAddedEntity(EntityEntry entry, EntityChangeReport changeReport) { CheckAndSetId(entry); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensions.cs new file mode 100644 index 0000000000..5f452c6319 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensions.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +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 EntityExtensions + { + private static readonly Dictionary ExtensionInfos; + + //TODO: Use PropertyBuilder instead + + static EntityExtensions() + { + ExtensionInfos = new Dictionary(); + } + + public static void AddProperty( + string name, + Type propertyType, + Action propertyBuildAction) + { + var extensionInfo = ExtensionInfos + .GetOrAdd(typeof(TEntity), () => new EntityExtensionInfo()); + + var propertyExtensionInfo = extensionInfo.Properties + .GetOrAdd(name, () => new PropertyExtensionInfo(propertyType)); + + propertyExtensionInfo.Actions.Add(propertyBuildAction); + } + + public static void ConfigureProperties(EntityTypeBuilder entityTypeBuilder) + { + var entityExtensionInfo = ExtensionInfos.GetOrDefault(typeof(TEntity)); + if (entityExtensionInfo == null) + { + return; + } + + foreach (var propertyExtensionInfo in entityExtensionInfo.Properties) + { + var property = entityTypeBuilder.Property(propertyExtensionInfo.Value.PropertyType, propertyExtensionInfo.Key); + foreach (var action in propertyExtensionInfo.Value.Actions) + { + 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/Modeling/AbpEntityTypeBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs index 6db09d4d17..3c54044bd2 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 @@ -58,7 +58,7 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling { b.Property>(nameof(IHasExtraProperties.ExtraProperties)) .HasColumnName(nameof(IHasExtraProperties.ExtraProperties)) - .HasConversion(new AbpJsonValueConverter>()) + .HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType)) .Metadata.SetValueComparer(new AbpDictionaryValueComparer()); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs index e77b2842a8..6ecf19fa98 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Newtonsoft.Json; +using Volo.Abp.Data; namespace Volo.Abp.EntityFrameworkCore.ValueConverters { @@ -7,9 +8,20 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters { public AbpJsonValueConverter() : base( - d => JsonConvert.SerializeObject(d, Formatting.None), - s => JsonConvert.DeserializeObject(s)) + d => SerializeObject(d), + s => DeserializeObject(s)) { + + } + + private static string SerializeObject(TPropertyType d) + { + return JsonConvert.SerializeObject(d, Formatting.None); + } + + private static TPropertyType DeserializeObject(string s) + { + return JsonConvert.DeserializeObject(s); } } } 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 new file mode 100644 index 0000000000..ffe3399e6a --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Newtonsoft.Json; +using Volo.Abp.EntityFrameworkCore.Extensions; + +namespace Volo.Abp.EntityFrameworkCore.ValueConverters +{ + public class ExtraPropertiesValueConverter : ValueConverter, string> + { + public ExtraPropertiesValueConverter(Type entityType) + : base( + d => SerializeObject(d, entityType), + s => DeserializeObject(s)) + { + + } + + private static string SerializeObject(Dictionary extraProperties, Type entityType) + { + var copyDictionary = new Dictionary(extraProperties); + + if (entityType != null) + { + var propertyNames = EntityExtensions.GetPropertyNames(entityType); + + foreach (var propertyName in propertyNames) + { + copyDictionary.Remove(propertyName); + } + } + + return JsonConvert.SerializeObject(copyDictionary, Formatting.None); + } + + private static Dictionary DeserializeObject(string extraPropertiesAsJson) + { + return JsonConvert.DeserializeObject>(extraPropertiesAsJson); + } + } +} \ No newline at end of file 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 76ac038c05..6d5d182a51 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,6 +1,7 @@ using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.Extensions; using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.Users.EntityFrameworkCore; @@ -36,11 +37,13 @@ namespace Volo.Abp.Identity.EntityFrameworkCore 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(); b.HasMany(u => u.Tokens).WithOne().HasForeignKey(ur => ur.UserId).IsRequired(); - + b.HasIndex(u => u.NormalizedUserName); b.HasIndex(u => u.NormalizedEmail); b.HasIndex(u => u.UserName);