From b4c18d0f9da6cf23fe42c5d8c8e1659ff24718eb Mon Sep 17 00:00:00 2001 From: Mihail Date: Fri, 20 Feb 2026 15:23:14 +0100 Subject: [PATCH 1/3] refactor: add optional include=* query parameter --- Dappi.SourceGenerator/CrudGenerator.cs | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/Dappi.SourceGenerator/CrudGenerator.cs b/Dappi.SourceGenerator/CrudGenerator.cs index fff6139..fbe45c2 100644 --- a/Dappi.SourceGenerator/CrudGenerator.cs +++ b/Dappi.SourceGenerator/CrudGenerator.cs @@ -151,6 +151,21 @@ private dynamic GetDbSetForType(string typeName) private IQueryable<{item.ClassName}> ApplyDynamicIncludes(IQueryable<{item.ClassName}> query) {{ + if (!HttpContext.Request.Query.ContainsKey(""include"")) + {{ + return query; + }} + + var shouldApplyFullIncludes = HttpContext.Request.Query[""include""] + .SelectMany(includeValue => includeValue + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + .Any(includePath => includePath == ""*""); + + if (shouldApplyFullIncludes) + {{ + return ApplyFullIncludes(query); + }} + var includeTree = HttpContext.Items[IncludeQueryFilter.IncludeParamsKey] as IDictionary; if (includeTree is null || includeTree.Count == 0) {{ @@ -165,6 +180,54 @@ private dynamic GetDbSetForType(string typeName) return query; }} +private IQueryable<{item.ClassName}> ApplyFullIncludes(IQueryable<{item.ClassName}> query) + {{ + var rootEntityType = dbContext.Model.FindEntityType(typeof({item.ClassName})); + if (rootEntityType is null) + {{ + return query; + }} + + var includePaths = new HashSet(StringComparer.OrdinalIgnoreCase); + var visitedTypes = new HashSet(StringComparer.OrdinalIgnoreCase); + visitedTypes.Add(rootEntityType.Name); + var prefix = string.Empty; + + CollectIncludePaths(rootEntityType, prefix, includePaths, visitedTypes); + + foreach (var includePath in includePaths) + {{ + query = query.Include(includePath); + }} + + return query; + }} + + private static void CollectIncludePaths( + Microsoft.EntityFrameworkCore.Metadata.IEntityType entityType, + string prefix, + HashSet includePaths, + HashSet visitedTypes) + {{ + var relations = entityType.GetNavigations(); + foreach (var relation in relations) + {{ + var entityTypeName = relation.TargetEntityType.Name; + if (visitedTypes.Contains(entityTypeName)) + {{ + continue; + }} + + var navigationPath = string.IsNullOrEmpty(prefix) ? relation.Name : string.Concat(prefix, ""."", relation.Name); + + includePaths.Add(navigationPath); + visitedTypes.Add(entityTypeName); + + CollectIncludePaths(relation.TargetEntityType, navigationPath, includePaths, visitedTypes); + visitedTypes.Remove(entityTypeName); + }} + }} + private static IQueryable<{item.ClassName}> ApplyIncludeRecursively(IQueryable<{item.ClassName}> query, string path, IncludeNode node) {{ query = query.Include(path); From 4bfe8d36f890a14f14a5c47768c740ce04783cb4 Mon Sep 17 00:00:00 2001 From: Mihail Date: Fri, 20 Feb 2026 15:30:17 +0100 Subject: [PATCH 2/3] refactor: remove TestController --- .../MyCompany.MyProject.WebApi/Controllers/TestController.cs | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 templates/MyCompany.MyProject.WebApi/Controllers/TestController.cs diff --git a/templates/MyCompany.MyProject.WebApi/Controllers/TestController.cs b/templates/MyCompany.MyProject.WebApi/Controllers/TestController.cs deleted file mode 100644 index ef87d91..0000000 --- a/templates/MyCompany.MyProject.WebApi/Controllers/TestController.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace MyCompany.MyProject.WebApi.Controllers; - -public class TestController { } From 02a9ba1f5182dc361109664725dd72e83399b18f Mon Sep 17 00:00:00 2001 From: Mihail Date: Fri, 20 Feb 2026 16:43:26 +0100 Subject: [PATCH 3/3] refactor: fix the null warning, reformat code, fix the "Content Manager" to display the related properties --- CCUI.DAPPI/src/app/state/content/content.effects.ts | 1 + Dappi.SourceGenerator/CrudGenerator.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CCUI.DAPPI/src/app/state/content/content.effects.ts b/CCUI.DAPPI/src/app/state/content/content.effects.ts index 53a8655..cccf51b 100644 --- a/CCUI.DAPPI/src/app/state/content/content.effects.ts +++ b/CCUI.DAPPI/src/app/state/content/content.effects.ts @@ -38,6 +38,7 @@ export class ContentEffects { offset: ((action.page - 1) * action.limit).toString(), limit: action.limit.toString(), SearchTerm: action.searchText || '', + include: '*', }, }) .pipe( diff --git a/Dappi.SourceGenerator/CrudGenerator.cs b/Dappi.SourceGenerator/CrudGenerator.cs index fbe45c2..81dfb18 100644 --- a/Dappi.SourceGenerator/CrudGenerator.cs +++ b/Dappi.SourceGenerator/CrudGenerator.cs @@ -157,7 +157,7 @@ private dynamic GetDbSetForType(string typeName) }} var shouldApplyFullIncludes = HttpContext.Request.Query[""include""] - .SelectMany(includeValue => includeValue + .SelectMany(includeValue => (includeValue ?? string.Empty) .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) .Any(includePath => includePath == ""*""); @@ -180,7 +180,7 @@ private dynamic GetDbSetForType(string typeName) return query; }} -private IQueryable<{item.ClassName}> ApplyFullIncludes(IQueryable<{item.ClassName}> query) + private IQueryable<{item.ClassName}> ApplyFullIncludes(IQueryable<{item.ClassName}> query) {{ var rootEntityType = dbContext.Model.FindEntityType(typeof({item.ClassName})); if (rootEntityType is null)