From 24283b2e1a63345d0a1897d8c4c46c843e4243cc Mon Sep 17 00:00:00 2001 From: Alexandr Makarov Date: Tue, 1 Oct 2024 11:57:16 +0700 Subject: [PATCH 1/4] Change way of getting calculated property metadata SN-733 --- .../Admin/ShopAdminConfiguration.cs | 6 ++ demo/Saritasa.NetForge.Demo/Models/Address.cs | 5 ++ .../Services/EfCoreMetadataService.cs | 73 ++++++++++++------- 3 files changed, 57 insertions(+), 27 deletions(-) diff --git a/demo/Saritasa.NetForge.Demo/Infrastructure/Admin/ShopAdminConfiguration.cs b/demo/Saritasa.NetForge.Demo/Infrastructure/Admin/ShopAdminConfiguration.cs index 7f5f7989..0c35abd0 100644 --- a/demo/Saritasa.NetForge.Demo/Infrastructure/Admin/ShopAdminConfiguration.cs +++ b/demo/Saritasa.NetForge.Demo/Infrastructure/Admin/ShopAdminConfiguration.cs @@ -65,6 +65,12 @@ public void Configure(EntityOptionsBuilder entityOptionsBuilder) .SetIsSortable(true) .SetSearchType(SearchType.ContainsCaseInsensitive); }) + .IncludeProperty(address => address.FullAddress, builder => + { + builder + .SetOrder(5) + .SetDisplayName("Full Address"); + }) .SetDisplayDetails(true); }) .IncludeNavigation(shop => shop.Products, navigationOptionsBuilder => diff --git a/demo/Saritasa.NetForge.Demo/Models/Address.cs b/demo/Saritasa.NetForge.Demo/Models/Address.cs index 2922e9bc..26a598e7 100644 --- a/demo/Saritasa.NetForge.Demo/Models/Address.cs +++ b/demo/Saritasa.NetForge.Demo/Models/Address.cs @@ -52,6 +52,11 @@ public class Address [Required] public required string Country { get; set; } + /// + /// Full address. + /// + public string FullAddress => $"{Country}, {Street}, {City}"; + /// /// The latitude coordinate of the address location. /// diff --git a/src/Saritasa.NetForge.Infrastructure.EfCore/Services/EfCoreMetadataService.cs b/src/Saritasa.NetForge.Infrastructure.EfCore/Services/EfCoreMetadataService.cs index f845a85b..46cfb08a 100644 --- a/src/Saritasa.NetForge.Infrastructure.EfCore/Services/EfCoreMetadataService.cs +++ b/src/Saritasa.NetForge.Infrastructure.EfCore/Services/EfCoreMetadataService.cs @@ -61,14 +61,13 @@ private static EntityMetadata GetEntityMetadata(IReadOnlyEntityType entityType) .Concat(entityType.GetSkipNavigations()) .Select(GetNavigationMetadata); - var propertiesMetadata = entityType.GetProperties().Select(GetPropertyMetadata); var entityMetadata = new EntityMetadata { DisplayName = entityType.ShortName(), ClrType = entityType.ClrType, Description = entityType.GetComment() ?? string.Empty, IsHidden = entityType.IsPropertyBag, - Properties = propertiesMetadata.ToList(), + Properties = GetPropertiesMetadata(entityType), Navigations = navigationsMetadata.ToList(), IsKeyless = entityType.FindPrimaryKey() is null }; @@ -76,30 +75,6 @@ private static EntityMetadata GetEntityMetadata(IReadOnlyEntityType entityType) return entityMetadata; } - /// - /// Retrieve metadata for a property of an entity type. - /// - /// The EF Core property to retrieve metadata for. - /// A object containing metadata information for the property. - private static PropertyMetadata GetPropertyMetadata(IReadOnlyProperty property) - { - var propertyMetadata = new PropertyMetadata - { - Name = property.Name, - Description = property.GetComment() ?? string.Empty, - ClrType = property.ClrType, - PropertyInformation = property.PropertyInfo, - IsForeignKey = property.IsForeignKey(), - IsPrimaryKey = property.IsPrimaryKey(), - IsNullable = property.IsNullable, - Order = property.GetColumnOrder(), - IsShadow = property.IsShadowProperty(), - IsValueGeneratedOnAdd = property.ValueGenerated.HasFlag(ValueGenerated.OnAdd), - IsValueGeneratedOnUpdate = property.ValueGenerated.HasFlag(ValueGenerated.OnUpdate), - }; - return propertyMetadata; - } - /// /// Retrieve metadata for a navigation of an entity type. /// @@ -121,7 +96,7 @@ private static NavigationMetadata GetNavigationMetadata(IReadOnlyNavigationBase { Name = navigation.Name, IsCollection = navigation.IsCollection, - TargetEntityProperties = navigation.TargetEntityType.GetProperties().Select(GetPropertyMetadata).ToList(), + TargetEntityProperties = GetPropertiesMetadata(navigation.TargetEntityType), PropertyInformation = navigation.PropertyInfo, ClrType = navigation.ClrType, IsNullable = isNullable @@ -129,4 +104,48 @@ private static NavigationMetadata GetNavigationMetadata(IReadOnlyNavigationBase return navigationMetadata; } + + private static List GetPropertiesMetadata(IReadOnlyEntityType entityType) + { + var propertiesMetadata = entityType.GetProperties().Select(GetPropertyMetadata); + + var reflectionProperties = entityType.ClrType + .GetProperties() + .Where(property => !propertiesMetadata.Any(metadata => metadata.Name == property.Name)); + + var calculatedProperties = reflectionProperties + .Where(property => property is { CanRead: true, CanWrite: false }) + .Select(propertyInfo => new PropertyMetadata + { + Name = propertyInfo.Name, + IsEditable = false, + PropertyInformation = propertyInfo, + IsCalculatedProperty = true + }); + return propertiesMetadata.Union(calculatedProperties).ToList(); + } + + /// + /// Retrieve metadata for a property of an entity type. + /// + /// The EF Core property to retrieve metadata for. + /// A object containing metadata information for the property. + private static PropertyMetadata GetPropertyMetadata(IReadOnlyProperty property) + { + var propertyMetadata = new PropertyMetadata + { + Name = property.Name, + Description = property.GetComment() ?? string.Empty, + ClrType = property.ClrType, + PropertyInformation = property.PropertyInfo, + IsForeignKey = property.IsForeignKey(), + IsPrimaryKey = property.IsPrimaryKey(), + IsNullable = property.IsNullable, + Order = property.GetColumnOrder(), + IsShadow = property.IsShadowProperty(), + IsValueGeneratedOnAdd = property.ValueGenerated.HasFlag(ValueGenerated.OnAdd), + IsValueGeneratedOnUpdate = property.ValueGenerated.HasFlag(ValueGenerated.OnUpdate), + }; + return propertyMetadata; + } } From 88a27310209b4632106ced88f4f957b809e50853 Mon Sep 17 00:00:00 2001 From: Alexandr Makarov Date: Tue, 1 Oct 2024 12:06:06 +0700 Subject: [PATCH 2/4] Remove old calculated properties logic SN-733 --- .../Admin/UserAdminConfiguration.cs | 1 - .../Entities/Options/EntityOptions.cs | 5 --- .../EntityOptionsBuilder.cs | 12 ------- .../MetadataTests/UserAdminConfiguration.cs | 1 - .../Metadata/Services/AdminMetadataService.cs | 34 ------------------- 5 files changed, 53 deletions(-) diff --git a/demo/Saritasa.NetForge.Demo/Infrastructure/Admin/UserAdminConfiguration.cs b/demo/Saritasa.NetForge.Demo/Infrastructure/Admin/UserAdminConfiguration.cs index fca72e2a..7369d640 100644 --- a/demo/Saritasa.NetForge.Demo/Infrastructure/Admin/UserAdminConfiguration.cs +++ b/demo/Saritasa.NetForge.Demo/Infrastructure/Admin/UserAdminConfiguration.cs @@ -14,7 +14,6 @@ public class UserAdminConfiguration : IEntityAdminConfiguration public void Configure(EntityOptionsBuilder entityOptionsBuilder) { entityOptionsBuilder.SetGroup(GroupConstants.Identity) - .AddCalculatedProperties(user => user.FullName, user => user.Age) .ConfigureProperty(user => user.FullName, builder => builder.SetOrder(1)) .ConfigureProperty(user => user.ConcurrencyStamp, builder => builder.SetIsHidden(true)) .ConfigureProperty(user => user.LockoutEnd, builder => builder.SetIsHidden(true)) diff --git a/src/Saritasa.NetForge.Domain/Entities/Options/EntityOptions.cs b/src/Saritasa.NetForge.Domain/Entities/Options/EntityOptions.cs index 837acd3a..97d7ffd7 100644 --- a/src/Saritasa.NetForge.Domain/Entities/Options/EntityOptions.cs +++ b/src/Saritasa.NetForge.Domain/Entities/Options/EntityOptions.cs @@ -50,11 +50,6 @@ public EntityOptions(Type entityType) /// public ICollection PropertyOptions { get; set; } = new List(); - /// - /// Collection of the calculated property names. - /// - public List CalculatedPropertyNames { get; } = new(); - /// public Func, string, IQueryable>? SearchFunction { get; set; } diff --git a/src/Saritasa.NetForge.DomainServices/EntityOptionsBuilder.cs b/src/Saritasa.NetForge.DomainServices/EntityOptionsBuilder.cs index 2aad4403..751ba0d0 100644 --- a/src/Saritasa.NetForge.DomainServices/EntityOptionsBuilder.cs +++ b/src/Saritasa.NetForge.DomainServices/EntityOptionsBuilder.cs @@ -114,18 +114,6 @@ public EntityOptionsBuilder ConfigureProperty( return this; } - /// - /// Adds calculated properties for the specified entity type. - /// - /// An array of lambda expressions representing calculated properties. - public EntityOptionsBuilder AddCalculatedProperties( - params Expression>[] propertyExpressions) - { - var propertyNames = propertyExpressions.Select(expression => expression.GetMemberName()); - options.CalculatedPropertyNames.AddRange(propertyNames); - return this; - } - /// /// Adds navigation property to the entity. /// diff --git a/src/Saritasa.NetForge.Tests/MetadataTests/UserAdminConfiguration.cs b/src/Saritasa.NetForge.Tests/MetadataTests/UserAdminConfiguration.cs index 718f55d2..e13baef2 100644 --- a/src/Saritasa.NetForge.Tests/MetadataTests/UserAdminConfiguration.cs +++ b/src/Saritasa.NetForge.Tests/MetadataTests/UserAdminConfiguration.cs @@ -14,7 +14,6 @@ public void Configure(EntityOptionsBuilder entityOptionsBuilder) { entityOptionsBuilder.SetGroup("Identity") .SetDescription("This is a description") - .AddCalculatedProperties(user => user.FullName, user => user.Age) .ConfigureProperty(user => user.FullName, builder => builder.SetOrder(1)) .ConfigureProperty(user => user.ConcurrencyStamp, builder => builder.SetIsHidden(true)) .ConfigureProperty(user => user.LockoutEnd, builder => builder.SetIsHidden(true)) diff --git a/src/Saritasa.NetForge.UseCases/Metadata/Services/AdminMetadataService.cs b/src/Saritasa.NetForge.UseCases/Metadata/Services/AdminMetadataService.cs index e48675ec..6a8eddd1 100644 --- a/src/Saritasa.NetForge.UseCases/Metadata/Services/AdminMetadataService.cs +++ b/src/Saritasa.NetForge.UseCases/Metadata/Services/AdminMetadataService.cs @@ -52,8 +52,6 @@ public IEnumerable GetMetadata() if (entityOptions != null) { - var calculatedProperties = GetCalculatedPropertiesMetadata(entityOptions); - entityMetadata.Properties.AddRange(calculatedProperties); entityMetadata.ApplyOptions(entityOptions, adminOptions); } @@ -65,38 +63,6 @@ public IEnumerable GetMetadata() return metadata; } - /// - /// Retrieves metadata for calculated properties defined in the entity options. - /// - /// The entity options that specify the calculated properties. - /// An enumerable collection of calculated property metadata. - private static IEnumerable GetCalculatedPropertiesMetadata(EntityOptions entityOptions) - { - var propertiesMetadata = new List(); - - foreach (var propertyName in entityOptions.CalculatedPropertyNames) - { - var propertyInformation = entityOptions.EntityType.GetProperty(propertyName); - - if (propertyInformation == null) - { - continue; - } - - var propertyMetadata = new PropertyMetadata - { - Name = propertyName, - IsEditable = false, - PropertyInformation = propertyInformation, - IsCalculatedProperty = true - }; - - propertiesMetadata.Add(propertyMetadata); - } - - return propertiesMetadata; - } - /// /// Try to get the entities metadata from the cache. /// From 76ecbbdc2f96e57112664fc784362d5a4b0b885c Mon Sep 17 00:00:00 2001 From: Alexandr Makarov Date: Tue, 1 Oct 2024 12:09:26 +0700 Subject: [PATCH 3/4] Remove calculated properties information from readme SN-733 --- README.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/README.md b/README.md index 9177823b..f25cc5b8 100644 --- a/README.md +++ b/README.md @@ -450,23 +450,6 @@ You can sort multiple properties at once. It can be achieved by pressing sort bu Sorting can be cancelled by pressing on it with `ALT`. -## Calculated Properties -Calculated properties are properties that don't have a direct representation in your database but are computed based on other existing properties. These properties can be useful for displaying calculated values in the admin panel. - -You can add calculated properties to your entities using the Fluent API: - -```csharp -services.AddNetForge(optionsBuilder => -{ - optionsBuilder.ConfigureEntity(entityOptionsBuilder => - { - entityOptionsBuilder.AddCalculatedProperties(user => user.FullName, user => user.Age); - }); - - // Other settings... -}); -``` - ## Display Properties as Title Case By default, all entity properties are displayed in Title Case. From 2d4ccee861678d19592851c375c800cd2a03706e Mon Sep 17 00:00:00 2001 From: Alexandr Makarov Date: Wed, 2 Oct 2024 11:38:23 +0700 Subject: [PATCH 4/4] Return information about calculated properties to readme SN-733 --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index f25cc5b8..66cacd3e 100644 --- a/README.md +++ b/README.md @@ -450,6 +450,19 @@ You can sort multiple properties at once. It can be achieved by pressing sort bu Sorting can be cancelled by pressing on it with `ALT`. +## Calculated Properties + +Calculated properties are properties that don't have a direct representation in your database but are computed based on other existing properties. These properties can be useful for displaying calculated values in the admin panel. + +They behave just like an ordinary property, and you can configure them in the similar way: + +```csharp +entityOptionsBuilder.ConfigureProperty(address => address.FullAddress, propertyBuilder => +{ + propertyBuilder.SetDisplayName("Full Address"); +}); +``` + ## Display Properties as Title Case By default, all entity properties are displayed in Title Case.