From ad4d9c65d655ad0a0d0a3f010480e84a8ed7f0cf Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Wed, 15 Jan 2025 08:02:23 +0100 Subject: [PATCH] Added relation support --- src/HopFrame.Core/Config/PropertyConfig.cs | 9 +++- src/HopFrame.Core/Config/TableConfig.cs | 2 +- .../Services/IContextExplorer.cs | 1 + src/HopFrame.Core/Services/ITableManager.cs | 7 +++- .../Implementations/ContextExplorer.cs | 13 +++++- .../Services/Implementations/TableManager.cs | 41 +++++++++++++++++-- .../Layout/HopFrameNavigation.razor | 1 - .../Components/Pages/HopFrameListView.razor | 4 +- .../Components/Pages/Home.razor | 19 ++++++++- testing/HopFrame.Testing/Models/Post.cs | 2 +- testing/HopFrame.Testing/Models/User.cs | 5 ++- testing/HopFrame.Testing/Program.cs | 4 ++ 12 files changed, 93 insertions(+), 15 deletions(-) diff --git a/src/HopFrame.Core/Config/PropertyConfig.cs b/src/HopFrame.Core/Config/PropertyConfig.cs index db966aa..00d8e0f 100644 --- a/src/HopFrame.Core/Config/PropertyConfig.cs +++ b/src/HopFrame.Core/Config/PropertyConfig.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Linq.Expressions; +using System.Reflection; namespace HopFrame.Core.Config; @@ -8,6 +9,7 @@ public class PropertyConfig(PropertyInfo info) { public bool List { get; set; } = true; public bool Sortable { get; set; } = true; public bool Searchable { get; set; } = true; + public PropertyInfo? DisplayedProperty { get; set; } } public class PropertyConfig(PropertyConfig config) { @@ -32,5 +34,10 @@ public class PropertyConfig(PropertyConfig config) { config.Searchable = searchable; return this; } + + public PropertyConfig DisplayedProperty(Expression> propertyExpression) { + config.DisplayedProperty = TableConfig.GetPropertyInfo(propertyExpression); + return this; + } } diff --git a/src/HopFrame.Core/Config/TableConfig.cs b/src/HopFrame.Core/Config/TableConfig.cs index c58edca..0ef1bcc 100644 --- a/src/HopFrame.Core/Config/TableConfig.cs +++ b/src/HopFrame.Core/Config/TableConfig.cs @@ -42,7 +42,7 @@ public class TableConfig(TableConfig innerConfig) { return this; } - private static PropertyInfo GetPropertyInfo(Expression> propertyLambda) { + internal static PropertyInfo GetPropertyInfo(Expression> propertyLambda) { if (propertyLambda.Body is not MemberExpression member) { throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property."); } diff --git a/src/HopFrame.Core/Services/IContextExplorer.cs b/src/HopFrame.Core/Services/IContextExplorer.cs index cc0e4a6..272d4aa 100644 --- a/src/HopFrame.Core/Services/IContextExplorer.cs +++ b/src/HopFrame.Core/Services/IContextExplorer.cs @@ -5,5 +5,6 @@ namespace HopFrame.Core.Services; public interface IContextExplorer { public IEnumerable GetTableNames(); public TableConfig? GetTable(string tableName); + public TableConfig? GetTable(Type tableEntity); public ITableManager? GetTableManager(string tableName); } \ No newline at end of file diff --git a/src/HopFrame.Core/Services/ITableManager.cs b/src/HopFrame.Core/Services/ITableManager.cs index 7ecdf2d..6b49d9e 100644 --- a/src/HopFrame.Core/Services/ITableManager.cs +++ b/src/HopFrame.Core/Services/ITableManager.cs @@ -1,8 +1,13 @@ -namespace HopFrame.Core.Services; +using System.Reflection; +using HopFrame.Core.Config; + +namespace HopFrame.Core.Services; public interface ITableManager { public IQueryable LoadPage(int page, int perPage = 20); public (IEnumerable, int) Search(string searchTerm, int page = 0, int perPage = 20); public int TotalPages(int perPage = 20); public Task DeleteItem(object item); + + public string DisplayProperty(object item, PropertyInfo info, TableConfig? tableConfig); } \ No newline at end of file diff --git a/src/HopFrame.Core/Services/Implementations/ContextExplorer.cs b/src/HopFrame.Core/Services/Implementations/ContextExplorer.cs index fc5c632..92126c9 100644 --- a/src/HopFrame.Core/Services/Implementations/ContextExplorer.cs +++ b/src/HopFrame.Core/Services/Implementations/ContextExplorer.cs @@ -23,6 +23,16 @@ internal sealed class ContextExplorer(HopFrameConfig config, IServiceProvider pr return null; } + public TableConfig? GetTable(Type tableEntity) { + foreach (var context in config.Contexts) { + var table = context.Tables.FirstOrDefault(table => table.TableType == tableEntity); + if (table is not null) + return table; + } + + return null; + } + public ITableManager? GetTableManager(string tableName) { foreach (var context in config.Contexts) { var table = context.Tables.FirstOrDefault(table => table.PropertyName == tableName); @@ -32,9 +42,10 @@ internal sealed class ContextExplorer(HopFrameConfig config, IServiceProvider pr if (dbContext is null) return null; var type = typeof(TableManager<>).MakeGenericType(table.TableType); - return Activator.CreateInstance(type, dbContext, table) as ITableManager; + return Activator.CreateInstance(type, dbContext, table, this) as ITableManager; } return null; } + } \ No newline at end of file diff --git a/src/HopFrame.Core/Services/Implementations/TableManager.cs b/src/HopFrame.Core/Services/Implementations/TableManager.cs index c45b86a..0f497c2 100644 --- a/src/HopFrame.Core/Services/Implementations/TableManager.cs +++ b/src/HopFrame.Core/Services/Implementations/TableManager.cs @@ -1,20 +1,23 @@ -using HopFrame.Core.Config; +using System.ComponentModel.DataAnnotations.Schema; +using System.Reflection; +using HopFrame.Core.Config; using Microsoft.EntityFrameworkCore; namespace HopFrame.Core.Services.Implementations; -internal sealed class TableManager(DbContext context, TableConfig config) : ITableManager where TModel : class { +internal sealed class TableManager(DbContext context, TableConfig config, IContextExplorer explorer) : ITableManager where TModel : class { public IQueryable LoadPage(int page, int perPage = 20) { var table = context.Set(); - return table + var data = IncludeForgeinKeys(table); + return data .Skip(page * perPage) .Take(perPage); } public (IEnumerable, int) Search(string searchTerm, int page = 0, int perPage = 20) { var table = context.Set(); - var all = table + var all = IncludeForgeinKeys(table) .AsEnumerable() .Where(item => ItemSearched(item, searchTerm)) .ToList(); @@ -46,4 +49,34 @@ internal sealed class TableManager(DbContext context, TableConfig config return false; } + + public string DisplayProperty(object item, PropertyInfo info, TableConfig? tableConfig) { + if (item is null) return string.Empty; + var prop = tableConfig?.Properties.Find(prop => prop.Info.Name == info.Name); + if (prop is null) return item.ToString() ?? string.Empty; + + var propValue = prop.Info.GetValue(item); + if (propValue is null || prop.DisplayedProperty is null) + return propValue?.ToString() ?? string.Empty; + + var innerConfig = explorer.GetTable(propValue.GetType()); + return DisplayProperty(propValue, prop.DisplayedProperty, innerConfig); + } + + private IQueryable IncludeForgeinKeys(IQueryable query) { + var pendingQuery = query; + + foreach (var property in config.Properties) { + var attr = property.Info + .GetCustomAttributes(true) + .FirstOrDefault(att => att is ForeignKeyAttribute) as ForeignKeyAttribute; + + if (attr is null) continue; + + pendingQuery = pendingQuery.Include(property.Info.Name); + } + + return pendingQuery; + } + } \ No newline at end of file diff --git a/src/HopFrame.Web/Components/Layout/HopFrameNavigation.razor b/src/HopFrame.Web/Components/Layout/HopFrameNavigation.razor index ec93b38..09f4e82 100644 --- a/src/HopFrame.Web/Components/Layout/HopFrameNavigation.razor +++ b/src/HopFrame.Web/Components/Layout/HopFrameNavigation.razor @@ -18,7 +18,6 @@ private string? _displayName; private string? _initials; - private string? _searchValue; protected override async Task OnInitializedAsync() { if (Config.DisplayUserInfo) { diff --git a/src/HopFrame.Web/Components/Pages/HopFrameListView.razor b/src/HopFrame.Web/Components/Pages/HopFrameListView.razor index 10e9d5b..2a221d2 100644 --- a/src/HopFrame.Web/Components/Pages/HopFrameListView.razor +++ b/src/HopFrame.Web/Components/Pages/HopFrameListView.razor @@ -18,7 +18,7 @@ @{ var dataIndex = 0; } @foreach (var property in _config!.Properties.Where(prop => prop.List)) { - + } @@ -27,7 +27,7 @@ - + diff --git a/testing/HopFrame.Testing/Components/Pages/Home.razor b/testing/HopFrame.Testing/Components/Pages/Home.razor index 60626de..c25ecb7 100644 --- a/testing/HopFrame.Testing/Components/Pages/Home.razor +++ b/testing/HopFrame.Testing/Components/Pages/Home.razor @@ -1,4 +1,5 @@ @page "/" +@using HopFrame.Testing.Models Home @@ -11,20 +12,32 @@ Welcome to your new Fluent Blazor app. @code { protected override async Task OnInitializedAsync() { + User? user = null; for (int i = 0; i < 100; i++) { var first = GenerateName(Random.Shared.Next(4, 6)); var last = GenerateName(Random.Shared.Next(4, 6)); var username = $"{first}.{last}"; - Context.Users.Add(new() { + + user = new() { Email = $"{username}-{Random.Shared.Next(0, 20)}@gmail.com", Id = Guid.CreateVersion7(), FirstName = first, LastName = last, Username = username - }); + }; + + Context.Users.Add(user); } await Context.SaveChangesAsync(); + + Context.Posts.Add(new() { + Caption = "Cool Post", + Content = "This post is cool", + Author = user + }); + + await Context.SaveChangesAsync(); } public static string GenerateName(int len) { @@ -46,3 +59,5 @@ Welcome to your new Fluent Blazor app. } } + using HopFrame.Testing.Models; + using System.Runtime.InteropServices; diff --git a/testing/HopFrame.Testing/Models/Post.cs b/testing/HopFrame.Testing/Models/Post.cs index 59290b7..4481134 100644 --- a/testing/HopFrame.Testing/Models/Post.cs +++ b/testing/HopFrame.Testing/Models/Post.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace HopFrame.Testing.Models; public class Post { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [MaxLength(255)] diff --git a/testing/HopFrame.Testing/Models/User.cs b/testing/HopFrame.Testing/Models/User.cs index f602fbf..b6448ae 100644 --- a/testing/HopFrame.Testing/Models/User.cs +++ b/testing/HopFrame.Testing/Models/User.cs @@ -1,6 +1,9 @@ -namespace HopFrame.Testing.Models; +using System.ComponentModel.DataAnnotations; + +namespace HopFrame.Testing.Models; public class User { + [Key] public required Guid Id { get; init; } public required string Email { get; init; } public string? Username { get; set; } diff --git a/testing/HopFrame.Testing/Program.cs b/testing/HopFrame.Testing/Program.cs index 44b37f8..5d5286d 100644 --- a/testing/HopFrame.Testing/Program.cs +++ b/testing/HopFrame.Testing/Program.cs @@ -35,6 +35,10 @@ builder.Services.AddHopFrame(options => { table.Property(u => u.Id) .Sortable(false); }); + + context.Table() + .Property(p => p.Author) + .DisplayedProperty(u => u!.Username); }); });