Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/sn 733 use calculated properties inside navigation #86

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
AlexanderMakarov124 marked this conversation as resolved.
Show resolved Hide resolved
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<User>(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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ public void Configure(EntityOptionsBuilder<Shop> entityOptionsBuilder)
.SetIsSortable(true)
.SetSearchType(SearchType.ContainsCaseInsensitive);
})
.IncludeProperty(address => address.FullAddress, builder =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we can apply the same Fluent API for the calculated properties. Some of the builder methods are useless for them, like SetIsExcludedFromQuery or smth. In the future, we can have even more.

{
builder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be sortable?

.SetOrder(5)
.SetDisplayName("Full Address");
})
.SetDisplayDetails(true);
})
.IncludeNavigation<Product>(shop => shop.Products, navigationOptionsBuilder =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public class UserAdminConfiguration : IEntityAdminConfiguration<User>
public void Configure(EntityOptionsBuilder<User> 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))
Expand Down
5 changes: 5 additions & 0 deletions demo/Saritasa.NetForge.Demo/Models/Address.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public class Address
[Required]
public required string Country { get; set; }

/// <summary>
/// Full address.
/// </summary>
public string FullAddress => $"{Country}, {Street}, {City}";

/// <summary>
/// The latitude coordinate of the address location.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ public EntityOptions(Type entityType)
/// </summary>
public ICollection<PropertyOptions> PropertyOptions { get; set; } = new List<PropertyOptions>();

/// <summary>
/// Collection of the calculated property names.
/// </summary>
public List<string> CalculatedPropertyNames { get; } = new();

/// <inheritdoc cref="EntityMetadata.SearchFunction"/>
public Func<IServiceProvider?, IQueryable<object>, string, IQueryable<object>>? SearchFunction { get; set; }

Expand Down
12 changes: 0 additions & 12 deletions src/Saritasa.NetForge.DomainServices/EntityOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,18 +114,6 @@ public EntityOptionsBuilder<TEntity> ConfigureProperty(
return this;
}

/// <summary>
/// Adds calculated properties for the specified entity type.
/// </summary>
/// <param name="propertyExpressions">An array of lambda expressions representing calculated properties.</param>
public EntityOptionsBuilder<TEntity> AddCalculatedProperties(
params Expression<Func<TEntity, object?>>[] propertyExpressions)
{
var propertyNames = propertyExpressions.Select(expression => expression.GetMemberName());
options.CalculatedPropertyNames.AddRange(propertyNames);
return this;
}

/// <summary>
/// Adds navigation property to the entity.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,45 +61,20 @@ private static EntityMetadata GetEntityMetadata(IReadOnlyEntityType entityType)
.Concat<IReadOnlyNavigationBase>(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
};

return entityMetadata;
}

/// <summary>
/// Retrieve metadata for a property of an entity type.
/// </summary>
/// <param name="property">The EF Core property to retrieve metadata for.</param>
/// <returns>A <see cref="PropertyMetadata"/> object containing metadata information for the property.</returns>
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;
}

/// <summary>
/// Retrieve metadata for a navigation of an entity type.
/// </summary>
Expand All @@ -121,12 +96,56 @@ 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
};

return navigationMetadata;
}

private static List<PropertyMetadata> 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();
}

/// <summary>
/// Retrieve metadata for a property of an entity type.
/// </summary>
/// <param name="property">The EF Core property to retrieve metadata for.</param>
/// <returns>A <see cref="PropertyMetadata"/> object containing metadata information for the property.</returns>
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public void Configure(EntityOptionsBuilder<User> 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ public IEnumerable<EntityMetadata> GetMetadata()

if (entityOptions != null)
{
var calculatedProperties = GetCalculatedPropertiesMetadata(entityOptions);
entityMetadata.Properties.AddRange(calculatedProperties);
entityMetadata.ApplyOptions(entityOptions, adminOptions);
}

Expand All @@ -65,38 +63,6 @@ public IEnumerable<EntityMetadata> GetMetadata()
return metadata;
}

/// <summary>
/// Retrieves metadata for calculated properties defined in the entity options.
/// </summary>
/// <param name="entityOptions">The entity options that specify the calculated properties.</param>
/// <returns>An enumerable collection of calculated property metadata.</returns>
private static IEnumerable<PropertyMetadata> GetCalculatedPropertiesMetadata(EntityOptions entityOptions)
{
var propertiesMetadata = new List<PropertyMetadata>();

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

/// <summary>
/// Try to get the entities metadata from the cache.
/// </summary>
Expand Down
Loading