diff --git a/README.md b/README.md index d86a16949..1dcc92fd0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# RSCG - 249 Examples of Roslyn Source Code Generators / 16 created by Microsoft / +# RSCG - 250 Examples of Roslyn Source Code Generators / 16 created by Microsoft / -The RSCG_Examples repository is a comprehensive documentation system that automatically processes and showcases 249 Roslyn Source Code Generator (RSCG) examples. The system transforms individual RSCG projects into structured documentation with code examples and cross-referenced content with a searchable website and code example exports. +The RSCG_Examples repository is a comprehensive documentation system that automatically processes and showcases 250 Roslyn Source Code Generator (RSCG) examples. The system transforms individual RSCG projects into structured documentation with code examples and cross-referenced content with a searchable website and code example exports. This system serves as both a learning resource for .NET developers interested in source generators and an automated pipeline for maintaining up-to-date documentation about the RSCG ecosystem -## Latest Update : 2025-12-16 => 16 December 2025 +## Latest Update : 2025-12-17 => 17 December 2025 If you want to see examples with code, please click ***[List V2](https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG)*** @@ -24,8 +24,30 @@ If you want to be notified each time I add a new RSCG example , please click htt ## Content -Those are the 249 Roslyn Source Code Generators that I have tested you can see and download source code example. +Those are the 250 Roslyn Source Code Generators that I have tested you can see and download source code example. ( including 16 from Microsoft ) +### 250. [Facet.Search](https://ignatandrei.github.io/RSCG_Examples/v2/docs/Facet.Search) , in the [Database](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#database) category + +Generated on : 2025-12-17 => 17 December 2025 + +
+ Expand + + + +Author: Tim Maes + +Compile-time faceted search generation - attributes and source generators + +Nuget: [https://www.nuget.org/packages/Facet.Search/](https://www.nuget.org/packages/Facet.Search/) + + +Link: [https://ignatandrei.github.io/RSCG_Examples/v2/docs/Facet.Search](https://ignatandrei.github.io/RSCG_Examples/v2/docs/Facet.Search) + +Source: [https://github.com/Tim-Maes/Facet.Search](https://github.com/Tim-Maes/Facet.Search) + +
+ ### 249. [Silhouette](https://ignatandrei.github.io/RSCG_Examples/v2/docs/Silhouette) , in the [Profiler](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#profiler) category Generated on : 2025-12-16 => 16 December 2025 diff --git a/later.md b/later.md index a9effcebd..dd17334f9 100644 --- a/later.md +++ b/later.md @@ -1,6 +1,6 @@ # Just later -## Latest Update : 2025-12-16 => 16 December 2025 +## Latest Update : 2025-12-17 => 17 December 2025 diff --git a/v2/.tours/Facet.Search.tour b/v2/.tours/Facet.Search.tour new file mode 100644 index 000000000..fdf797fa8 --- /dev/null +++ b/v2/.tours/Facet.Search.tour @@ -0,0 +1,54 @@ + +{ + "$schema": "https://aka.ms/codetour-schema", + "title": "Facet.Search", + "steps": + [ + { + "file": "rscg_examples/Facet.Search/src/SearchDemo/SearchDemo.csproj", + "description": "First, we add Nuget [Facet.Search](https://www.nuget.org/packages/Facet.Search/) in csproj ", + "pattern": "Facet.Search" + } + + ,{ + "file": "rscg_examples/Facet.Search/src/SearchDemo/Person.cs", + "description": "File Person.cs ", + "pattern": "this is the code" + } + + ,{ + "file": "rscg_examples/Facet.Search/src/SearchDemo/Program.cs", + "description": "File Program.cs \r\n>> dotnet run --project rscg_examples/Facet.Search/src/SearchDemo/SearchDemo.csproj ", + "pattern": "this is the code" + } + + + ,{ + "file": "rscg_examples/Facet.Search/src/SearchDemo/obj/GX/Facet.Search.Generators/Facet.Search.Generators.FacetSearchGenerator/PersonSearchMetadata.g.cs", + "description": "Generated File 4 from 4 : PersonSearchMetadata.g.cs ", + "line": 1 + } + + ,{ + "file": "rscg_examples/Facet.Search/src/SearchDemo/obj/GX/Facet.Search.Generators/Facet.Search.Generators.FacetSearchGenerator/PersonSearchFilter.g.cs", + "description": "Generated File 3 from 4 : PersonSearchFilter.g.cs ", + "line": 1 + } + + ,{ + "file": "rscg_examples/Facet.Search/src/SearchDemo/obj/GX/Facet.Search.Generators/Facet.Search.Generators.FacetSearchGenerator/PersonSearchExtensions.g.cs", + "description": "Generated File 2 from 4 : PersonSearchExtensions.g.cs ", + "line": 1 + } + + ,{ + "file": "rscg_examples/Facet.Search/src/SearchDemo/obj/GX/Facet.Search.Generators/Facet.Search.Generators.FacetSearchGenerator/PersonFacetAggregations.g.cs", + "description": "Generated File 1 from 4 : PersonFacetAggregations.g.cs ", + "line": 1 + } + + ], + + "ref": "main" + +} \ No newline at end of file diff --git a/v2/Generator/all.csv b/v2/Generator/all.csv index c060238c1..8e3df5af5 100644 --- a/v2/Generator/all.csv +++ b/v2/Generator/all.csv @@ -248,3 +248,4 @@ Nr,Key,Source,Category 247,BlazorOcticons, https://github.com/BlazorOcticons/BlazorOcticons,Blazor 248,docopt.net, https://github.com/docopt/docopt.net,CommandLine 249,Silhouette, https://github.com/kevingosse/Silhouette,Profiler +250,Facet.Search, https://github.com/Tim-Maes/Facet.Search,Database diff --git a/v2/RSCGExamplesData/GeneratorDataRec.json b/v2/RSCGExamplesData/GeneratorDataRec.json index d013a1982..42558c7c0 100644 --- a/v2/RSCGExamplesData/GeneratorDataRec.json +++ b/v2/RSCGExamplesData/GeneratorDataRec.json @@ -1509,5 +1509,11 @@ "Category": 44, "dtStart": "2025-12-16T00:00:00", "show": true + }, + { + "ID":"Facet.Search", + "Category": 14, + "dtStart": "2025-12-17T00:00:00", + "show": true } ] \ No newline at end of file diff --git a/v2/book/examples/Facet.Search.html b/v2/book/examples/Facet.Search.html new file mode 100644 index 000000000..9081d4cba --- /dev/null +++ b/v2/book/examples/Facet.Search.html @@ -0,0 +1,74 @@ + +

RSCG nr 250 : Facet.Search

+ +

Info

+Nuget : https://www.nuget.org/packages/Facet.Search/ + +

You can find more details at : https://github.com/Tim-Maes/Facet.Search

+ +

Author :Tim Maes

+ +

Source: https://github.com/Tim-Maes/Facet.Search

+ +

About

+ +Generating search from C# clasess and propertiesIntegrating search in .NET applications + +

+ How to use +

+

+ Add reference to the Facet.Search in the csproj +

+ + +

This was for me the starting code

+ +
+ I have coded the file Program.cs +
+ +
+ +
+ I have coded the file Person.cs +
+ +
+

And here are the generated files

+ +
+ The file generated is PersonFacetAggregations.g.cs +
+ + +
+ The file generated is PersonSearchExtensions.g.cs +
+ + +
+ The file generated is PersonSearchFilter.g.cs +
+ + +
+ The file generated is PersonSearchMetadata.g.cs +
+ + +

+ You can download the code and this page as pdf from + + https://ignatandrei.github.io/RSCG_Examples/v2/docs/Facet.Search + +

+ + +

+ You can see the whole list at + + https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG + +

+ diff --git a/v2/book/examples/RSCG_ExportDiagram.html b/v2/book/examples/RSCG_ExportDiagram.html index 1b7dc9d49..e634fc9f6 100644 --- a/v2/book/examples/RSCG_ExportDiagram.html +++ b/v2/book/examples/RSCG_ExportDiagram.html @@ -6,7 +6,7 @@

Info

You can find more details at : RSCG_ExportDiagram

-

Author :AndreiIgnat

+

Author :Ignat Andrei

Source: https://github.com/ignatandrei/RSCG_ExportDiagram

diff --git a/v2/book/examples/Silhouette.html b/v2/book/examples/Silhouette.html index 8f663e7a8..987bf3d6e 100644 --- a/v2/book/examples/Silhouette.html +++ b/v2/book/examples/Silhouette.html @@ -28,6 +28,12 @@

This was for me the starting code

I have coded the file Program.cs
+
+ +
+ I have coded the file MyProfiler.cs +
+

And here are the generated files

diff --git a/v2/book/list.html b/v2/book/list.html index a2fa0391e..a62de17c8 100644 --- a/v2/book/list.html +++ b/v2/book/list.html @@ -17,7 +17,7 @@

-This is the list of 249 RSCG with examples => +This is the list of 250 RSCG with examples =>

@@ -1022,6 +1022,10 @@

+ + + +
249 Silhouette
250Facet.Search
diff --git a/v2/book/pandocHTML.yaml b/v2/book/pandocHTML.yaml index 440c9ae72..9eb967d6c 100644 --- a/v2/book/pandocHTML.yaml +++ b/v2/book/pandocHTML.yaml @@ -263,6 +263,7 @@ input-files: - examples/BlazorOcticons.html - examples/docopt.net.html - examples/Silhouette.html +- examples/Facet.Search.html # or you may use input-file: with a single value # defaults: diff --git a/v2/rscg_examples/Facet.Search/description.json b/v2/rscg_examples/Facet.Search/description.json new file mode 100644 index 000000000..a5063574d --- /dev/null +++ b/v2/rscg_examples/Facet.Search/description.json @@ -0,0 +1,22 @@ +{ + "generator":{ + "name":"Facet.Search", + "nuget":[ + "https://www.nuget.org/packages/Facet.Search/" + ], + "link":"https://github.com/Tim-Maes/Facet.Search", + "author":"Tim Maes", + "source":"https://github.com/Tim-Maes/Facet.Search" + }, + "data":{ + "goodFor":["Generating search from C# clasess and properties","Integrating search in .NET applications"], + "csprojDemo":"SearchDemo.csproj", + "csFiles":["Program.cs","Person.cs"], + "excludeDirectoryGenerated":[""], + "includeAdditionalFiles":[""] + }, + "links":{ + "blog":"", + "video":"" + } +} \ No newline at end of file diff --git a/v2/rscg_examples/Facet.Search/nuget.txt b/v2/rscg_examples/Facet.Search/nuget.txt new file mode 100644 index 000000000..0ef7f2069 --- /dev/null +++ b/v2/rscg_examples/Facet.Search/nuget.txt @@ -0,0 +1 @@ +Compile-time faceted search generation - attributes and source generators \ No newline at end of file diff --git a/v2/rscg_examples/Facet.Search/readme.txt b/v2/rscg_examples/Facet.Search/readme.txt new file mode 100644 index 000000000..e56c96bb2 --- /dev/null +++ b/v2/rscg_examples/Facet.Search/readme.txt @@ -0,0 +1,287 @@ +# Facet.Search + +[![CI](https://github.com/Tim-Maes/Facet.Search/actions/workflows/build.yml/badge.svg)](https://github.com/Tim-Maes/Facet.Search/actions/workflows/build.yml) +[![Test](https://github.com/Tim-Maes/Facet.Search/actions/workflows/test.yml/badge.svg)](https://github.com/Tim-Maes/Facet.Search/actions/workflows/test.yml) +[![CD](https://github.com/Tim-Maes/Facet.Search/actions/workflows/release.yml/badge.svg)](https://github.com/Tim-Maes/Facet.Search/actions/workflows/release.yml) +[![NuGet](https://img.shields.io/nuget/v/Facet.Search.svg)](https://www.nuget.org/packages/Facet.Search) +[![Downloads](https://img.shields.io/nuget/dt/Facet.Search.svg)](https://www.nuget.org/packages/Facet.Search) +[![GitHub](https://img.shields.io/github/license/Tim-Maes/Facet.Search.svg)](https://github.com/Tim-Maes/Facet.Search/blob/main/LICENSE.txt) +[![Discord](https://img.shields.io/discord/1443287393825329223?color=%237289da&label=Discord&logo=discord&logoColor=%237289da&style=flat-square)](https://discord.gg/yGDBhGuNMB) + +**Compile-time faceted search generation for .NET**, Zero boilerplate, type-safe, and performant. + +Facet.Search uses source generators to automatically create search filter classes, LINQ extension methods, facet aggregations, and metadata from your domain models, all at compile time with no runtime overhead. + +## Features + +- **Zero Boilerplate** - Just add attributes to your models +- **Type-Safe** - All filters are compile-time checked +- **Performant** - Generated code is as efficient as hand-written +- **SQL Translated** - All filters execute on the database, not in memory +- **EF Core Integration** - [Extensions for EF Core](https://github.com/Tim-Maes/Facet.Search/tree/master/src/Facet.Search.EFCore) & multiple DB providers +- **Full-Text Search** - Built-in text search with multiple strategies +- **Facet Aggregations** - Automatic counting and range detection +- **Frontend Metadata** - Generate facet metadata for UI consumption + +## Installation + +```bash +dotnet add package Facet.Search +``` + +For Entity Framework Core integration: +```bash +dotnet add package Facet.Search.EFCore +``` + +## Quick Start + +### 1. Define Your Model + +```csharp +using Facet.Search; + +[FacetedSearch] +public class Product +{ + public int Id { get; set; } + + [FullTextSearch] + public string Name { get; set; } = null!; + + [FullTextSearch(Weight = 0.5f)] + public string? Description { get; set; } + + [SearchFacet(Type = FacetType.Categorical, DisplayName = "Brand")] + public string Brand { get; set; } = null!; + + [SearchFacet(Type = FacetType.Range, DisplayName = "Price")] + public decimal Price { get; set; } + + [SearchFacet(Type = FacetType.Boolean, DisplayName = "In Stock")] + public bool InStock { get; set; } + + [SearchFacet(Type = FacetType.DateRange, DisplayName = "Created Date")] + public DateTime CreatedAt { get; set; } +} +``` + +### 2. Use Generated Code + +The source generator automatically creates: +- `ProductSearchFilter` > Filter class with all facet properties +- `ProductSearchExtensions` > LINQ extension methods +- `ProductFacetAggregations` > Aggregation results +- `ProductSearchMetadata` > Facet metadata for frontends + +```csharp +using YourNamespace.Search; + +// Create a filter +var filter = new ProductSearchFilter +{ + Brand = ["Apple", "Samsung"], + MinPrice = 100m, + MaxPrice = 1000m, + InStock = true, + SearchText = "laptop" +}; + +// Apply to any IQueryable +var results = products.AsQueryable() + .ApplyFacetedSearch(filter) + .ToList(); + +// Get facet aggregations +var aggregations = products.AsQueryable().GetFacetAggregations(); +// aggregations.Brand = { "Apple": 5, "Samsung": 3, ... } +// aggregations.PriceMin = 99.99m +// aggregations.PriceMax = 2499.99m + +// Access metadata for UI +foreach (var facet in ProductSearchMetadata.Facets) +{ + Console.WriteLine($"{facet.DisplayName} ({facet.Type})"); +} +``` + +## How It Works with EF Core + +**All generated filters are translated to SQL**, no client-side evaluation for facet filters. + +| Filter Type | Generated Code | SQL Translation | +|-------------|---------------|-----------------| +| Categorical | `.Where(x => filter.Brand.Contains(x.Brand))` | `WHERE Brand IN ('Apple', 'Samsung')` | +| Range | `.Where(x => x.Price >= min && x.Price <= max)` | `WHERE Price >= @min AND Price <= @max` | +| Boolean | `.Where(x => x.InStock == true)` | `WHERE InStock = 1` | +| DateRange | `.Where(x => x.CreatedAt >= from)` | `WHERE CreatedAt >= @from` | +| Full-Text | `.Where(x => x.Name.Contains(term))` | `WHERE Name LIKE '%term%'` | + +### Full-Text Search Strategies + +By default, full-text search uses `LIKE '%term%'` which works with all databases but doesn't use full-text indexes. You can configure different strategies: + +```csharp +[FacetedSearch(FullTextStrategy = FullTextSearchStrategy.LinqContains)] // Default: LIKE '%term%' +[FacetedSearch(FullTextStrategy = FullTextSearchStrategy.SqlServerFreeText)] // SQL Server FREETEXT +[FacetedSearch(FullTextStrategy = FullTextSearchStrategy.PostgreSqlFullText)] // PostgreSQL tsvector +[FacetedSearch(FullTextStrategy = FullTextSearchStrategy.ClientSide)] // In-memory (use with caution) +public class Product { } +``` + +| Strategy | Database | Requires Index | Performance | +|----------|----------|----------------|-------------| +| `LinqContains` | All | No | Slow on large data | +| `EfLike` | All | No | Same as LinqContains | +| `SqlServerFreeText` | SQL Server | FULLTEXT index | Fast | +| `SqlServerContains` | SQL Server | FULLTEXT index | Fast | +| `PostgreSqlFullText` | PostgreSQL | GIN index | Fast | +| `ClientSide` | N/A | No | Loads to memory | + +## EF Core Integration + +Use the `Facet.Search.EFCore` package for async operations: + +```csharp +using Facet.Search.EFCore; + +// Async search execution +var results = await dbContext.Products + .ApplyFacetedSearch(filter) + .ExecuteSearchAsync(); + +// Paginated results +var pagedResult = await dbContext.Products + .ApplyFacetedSearch(filter) + .ToPagedResultAsync(page: 1, pageSize: 20); + +// pagedResult.Items, pagedResult.TotalCount, pagedResult.TotalPages + +// Async facet aggregation +var brandCounts = await dbContext.Products + .AggregateFacetAsync(p => p.Brand, limit: 10); + +// Get min/max range +var (minPrice, maxPrice) = await dbContext.Products + .GetRangeAsync(p => p.Price); +``` + +## Facet Types + +| Type | Description | Generated Filter Properties | +|------|-------------|----------------------------| +| `Categorical` | Discrete values (Brand, Category) | `string[]? PropertyName` | +| `Range` | Numeric ranges (Price, Rating) | `decimal? MinPropertyName`, `decimal? MaxPropertyName` | +| `Boolean` | True/false filters (InStock) | `bool? PropertyName` | +| `DateRange` | Date/time ranges | `DateTime? PropertyNameFrom`, `DateTime? PropertyNameTo` | +| `Hierarchical` | Nested categories | `string[]? PropertyName` | + +## Attributes Reference + +### `[FacetedSearch]` + +Marks a class for search generation. + +```csharp +[FacetedSearch( + FilterClassName = "CustomFilter", // Custom filter class name + GenerateAggregations = true, // Generate aggregation methods + GenerateMetadata = true, // Generate metadata class + Namespace = "Custom.Namespace", // Custom namespace for generated code + FullTextStrategy = FullTextSearchStrategy.LinqContains // Full-text search strategy +)] +public class Product { } +``` + +### `[SearchFacet]` + +Marks a property as a filterable facet. + +```csharp +[SearchFacet( + Type = FacetType.Categorical, // Facet type + DisplayName = "Product Brand", // UI display name + OrderBy = FacetOrder.Count, // Aggregation ordering (Count, Alphabetical) + Limit = 10, // Max aggregation values + DependsOn = "Category", // Dependent facet + IsHierarchical = false, // Hierarchical category + NavigationPath = "Category.Name", // For navigation properties + AutoInclude = true // Auto-include in EF Core queries +)] +public string Brand { get; set; } +``` + +### `[FullTextSearch]` + +Marks a property for full-text search. + +```csharp +[FullTextSearch( + Weight = 1.0f, // Search relevance weight (higher = more important) + CaseSensitive = false, // Case sensitivity + Behavior = TextSearchBehavior.Contains // Match behavior: Contains, StartsWith, EndsWith, Exact +)] +public string Name { get; set; } +``` + +### `[Searchable]` + +Marks a property as searchable but not a facet (useful for sorting). + +```csharp +[Searchable(Sortable = true)] +public int Rating { get; set; } +``` + +## Navigation Properties + +Filter on related entities by specifying the navigation path: + +```csharp +public class Product +{ + [SearchFacet( + Type = FacetType.Categorical, + DisplayName = "Category", + NavigationPath = "Category.Name" // Filter on Category.Name + )] + public Category Category { get; set; } +} +``` + +## Generated Code Location + +Generated files appear in your project's `obj` folder: +``` +obj/Debug/net8.0/generated/Facet.Search.Generators/ +> ProductSearchFilter.g.cs +> ProductSearchExtensions.g.cs +> ProductFacetAggregations.g.cs +> ProductSearchMetadata.g.cs +``` + +## Performance Tips + +1. **Use database indexes** on facet columns for fast filtering +2. **Use full-text indexes** for text search on large datasets +3. **Apply filters before pagination** to reduce data transfer +4. **Cache aggregations** if they don't change frequently +5. **Use `Limit` on categorical facets** to avoid loading all distinct values + +## Requirements + +- .NET Standard 2.0+ (for Facet.Search) +- .NET 10+ (for Facet.Search.EFCore) +- C# 9.0+ + +## License + +MIT License — see [LICENSE.txt](LICENSE.txt) for details. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## Related Projects + +- [Facet](https://github.com/Tim-Maes/Facet) — The Facet ecosystem diff --git a/v2/rscg_examples/Facet.Search/src/SearchDemo.slnx b/v2/rscg_examples/Facet.Search/src/SearchDemo.slnx new file mode 100644 index 000000000..17e3e04bd --- /dev/null +++ b/v2/rscg_examples/Facet.Search/src/SearchDemo.slnx @@ -0,0 +1,3 @@ + + + diff --git a/v2/rscg_examples/Facet.Search/src/SearchDemo/Person.cs b/v2/rscg_examples/Facet.Search/src/SearchDemo/Person.cs new file mode 100644 index 000000000..6c037837f --- /dev/null +++ b/v2/rscg_examples/Facet.Search/src/SearchDemo/Person.cs @@ -0,0 +1,41 @@ +using Facet.Search; +using Microsoft.EntityFrameworkCore; + +namespace SearchDemo; + +[FacetedSearch] +public class Person +{ + public int Id { get; set; } + [FullTextSearch] + public string Name { get; set; }= string.Empty; + + [SearchFacet(Type = FacetType.DateRange, DisplayName = "Date Of Birth")] + public DateTime DOB { get; set; } + [SearchFacet(Type = FacetType.Range, DisplayName = "SalaryRange")] + public int Salary { get; set; } + [SearchFacet(Type = FacetType.Boolean, DisplayName = "IsEmployee")] + public bool IsActive { get; set; } +} + +public class MyAppContext : DbContext +{ + public MyAppContext() + { + this.Person =this.Set(); + //fake + Person.Add(new Person() { + DOB= new DateTime(1970,4,16), + Id=1, + IsActive=true, + Name="Andrei Ignat", + Salary= 3 + }); + } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlServer("Server=myServerAddress;Database=myDataBase;Trusted_Connection=True;"); + base.OnConfiguring(optionsBuilder); + } + public DbSet Person { get; set; } +} \ No newline at end of file diff --git a/v2/rscg_examples/Facet.Search/src/SearchDemo/Program.cs b/v2/rscg_examples/Facet.Search/src/SearchDemo/Program.cs new file mode 100644 index 000000000..6a7573f6b --- /dev/null +++ b/v2/rscg_examples/Facet.Search/src/SearchDemo/Program.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using SearchDemo; +using SearchDemo.Search; + +var filter = new PersonSearchFilter +{ + DOBFrom = new DateTime(1970, 1, 1), + DOBTo = new DateTime(1980, 12, 31), + IsActive = true, + MinSalary=1, + MaxSalary= 10, + SearchText= "Andrei" + + +}; + +MyAppContext cnt = new (); +var p= cnt.Person.ApplyFacetedSearch(filter); +var sql = p.ToQueryString(); +Console.WriteLine(sql); \ No newline at end of file diff --git a/v2/rscg_examples/Facet.Search/src/SearchDemo/SearchDemo.csproj b/v2/rscg_examples/Facet.Search/src/SearchDemo/SearchDemo.csproj new file mode 100644 index 000000000..adaa2cc3b --- /dev/null +++ b/v2/rscg_examples/Facet.Search/src/SearchDemo/SearchDemo.csproj @@ -0,0 +1,19 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + true + $(BaseIntermediateOutputPath)\GX + + diff --git a/v2/rscg_examples/Facet.Search/video.json b/v2/rscg_examples/Facet.Search/video.json new file mode 100644 index 000000000..834e67c93 --- /dev/null +++ b/v2/rscg_examples/Facet.Search/video.json @@ -0,0 +1,39 @@ +{ + "scriptName": "Facet.Search", + "steps": +[ + {"typeStep":"exec","arg":"clipchamp.exe launch"}, + {"typeStep":"text","arg": "Welcome to Roslyn Examples"}, + {"typeStep":"text","arg":"If you want to see more examples , see List Of RSCG"}, + {"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG"}, + {"typeStep":"text","arg": "My name is Andrei Ignat and I am deeply fond of Roslyn Source Code Generator. "}, + +{"typeStep":"text","arg": "Today I will present Facet.Search . Generating search from C# clasess and propertiesIntegrating search in .NET applications ."}, +{"typeStep":"browser","arg":"https://www.nuget.org/packages/Facet.Search/"}, +{"typeStep":"text","arg": "The whole example is here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/Facet.Search"}, +{"typeStep":"text","arg": "You can download the code from here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/Facet.Search#download-example-net--c-"}, +{"typeStep":"text","arg":"Here is the code downloaded "}, +{"typeStep":"exec","arg":"explorer.exe /select,D:\\gth\\RSCG_Examples\\v2\\Generator.sln"}, +{"typeStep":"text","arg": "So , let's start the project with Visual Studio Code "}, +{"typeStep":"stepvscode","arg": "-n D:\\gth\\RSCG_Examples\\v2"}, + +{"typeStep":"text","arg": "To use it ,you will put the Nuget Facet.Search into the csproj "}, + +{"typeStep":"stepvscode","arg": "-r -g D:\\gth\\RSCG_Examples\\v2\\rscg_examples\\Facet.Search\\src\\SearchDemo\\SearchDemo.csproj"}, + +{"typeStep":"text","arg": "And now I will show you an example of using Facet.Search"}, + +{"typeStep":"hide","arg": "now execute the tour in VSCode"}, +{"typeStep":"tour", "arg": "src/.tours/"}, +{"typeStep":"text","arg":" And I will execute the project"}, +{"typeStep":"showproj", "arg":"SearchDemo.csproj"}, +{"typeStep":"text","arg":" This concludes the project"}, +{"typeStep":"waitseconds","arg":"30"}, +{"typeStep":"text","arg": "Remember, you can download the code from here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/Facet.Search#download-example-net--c-", +SpeakTest=" "}, +{"typeStep":"waitseconds","arg":"30"}, +] +} diff --git a/v2/rscg_examples_site/docs/Authors/Tim_Maes.md b/v2/rscg_examples_site/docs/Authors/Tim_Maes.md index 09a2cb482..a3b3180c6 100644 --- a/v2/rscg_examples_site/docs/Authors/Tim_Maes.md +++ b/v2/rscg_examples_site/docs/Authors/Tim_Maes.md @@ -1,7 +1,9 @@ # Author : Tim Maes -Number RSCG: 1 +Number RSCG: 2 1 [Facet](/docs/Facet) [![Nuget](https://img.shields.io/nuget/dt/Facet?label=Facet)](https://www.nuget.org/packages/Facet/) ![GitHub Repo stars](https://img.shields.io/github/stars/Tim-Maes/Facet?style=social) 2025-08-17 + 2 [Facet.Search](/docs/Facet.Search) [![Nuget](https://img.shields.io/nuget/dt/Facet.Search?label=Facet.Search)](https://www.nuget.org/packages/Facet.Search/) ![GitHub Repo stars](https://img.shields.io/github/stars/Tim-Maes/Facet.Search?style=social) 2025-12-17 + diff --git a/v2/rscg_examples_site/docs/Categories/Database.md b/v2/rscg_examples_site/docs/Categories/Database.md index c4cc6f9ce..469cfc5ba 100644 --- a/v2/rscg_examples_site/docs/Categories/Database.md +++ b/v2/rscg_examples_site/docs/Categories/Database.md @@ -1,6 +1,6 @@

Database

-Number RSCG: 7 +Number RSCG: 8 1 [Breezy](/docs/Breezy) [![Nuget](https://img.shields.io/nuget/dt/Breezy.SourceGenerator?label=Breezy.SourceGenerator)](https://www.nuget.org/packages/Breezy.SourceGenerator/) ![GitHub Repo stars](https://img.shields.io/github/stars/Ludovicdln/Breezy?style=social) 2023-08-09 @@ -8,11 +8,13 @@ Number RSCG: 7 3 [EntityLengths.Generator](/docs/EntityLengths.Generator) [![Nuget](https://img.shields.io/nuget/dt/EntityLengths.Generator?label=EntityLengths.Generator)](https://www.nuget.org/packages/EntityLengths.Generator/) ![GitHub Repo stars](https://img.shields.io/github/stars/TarasKovalenko/EntityLengths.Generator?style=social) 2025-02-19 - 4 [Finch.Generators](/docs/Finch.Generators) [![Nuget](https://img.shields.io/nuget/dt/Finch.Generators?label=Finch.Generators)](https://www.nuget.org/packages/Finch.Generators/) ![GitHub Repo stars](https://img.shields.io/github/stars/ivmazurenko/finch?style=social) 2025-08-10 + 4 [Facet.Search](/docs/Facet.Search) [![Nuget](https://img.shields.io/nuget/dt/Facet.Search?label=Facet.Search)](https://www.nuget.org/packages/Facet.Search/) ![GitHub Repo stars](https://img.shields.io/github/stars/Tim-Maes/Facet.Search?style=social) 2025-12-17 - 5 [Gedaq](/docs/Gedaq) [![Nuget](https://img.shields.io/nuget/dt/Gedaq?label=Gedaq)](https://www.nuget.org/packages/Gedaq/) ![GitHub Repo stars](https://img.shields.io/github/stars/SoftStoneDevelop/Gedaq?style=social) 2023-07-29 + 5 [Finch.Generators](/docs/Finch.Generators) [![Nuget](https://img.shields.io/nuget/dt/Finch.Generators?label=Finch.Generators)](https://www.nuget.org/packages/Finch.Generators/) ![GitHub Repo stars](https://img.shields.io/github/stars/ivmazurenko/finch?style=social) 2025-08-10 - 6 [TableStorage](/docs/TableStorage) [![Nuget](https://img.shields.io/nuget/dt/TableStorage?label=TableStorage)](https://www.nuget.org/packages/TableStorage/) ![GitHub Repo stars](https://img.shields.io/github/stars/StevenThuriot/TableStorage?style=social) 2024-06-01 + 6 [Gedaq](/docs/Gedaq) [![Nuget](https://img.shields.io/nuget/dt/Gedaq?label=Gedaq)](https://www.nuget.org/packages/Gedaq/) ![GitHub Repo stars](https://img.shields.io/github/stars/SoftStoneDevelop/Gedaq?style=social) 2023-07-29 - 7 [Unflat](/docs/Unflat) [![Nuget](https://img.shields.io/nuget/dt/Unflat?label=Unflat)](https://www.nuget.org/packages/Unflat/) ![GitHub Repo stars](https://img.shields.io/github/stars/pstlnce/unflat?style=social) 2025-08-18 + 7 [TableStorage](/docs/TableStorage) [![Nuget](https://img.shields.io/nuget/dt/TableStorage?label=TableStorage)](https://www.nuget.org/packages/TableStorage/) ![GitHub Repo stars](https://img.shields.io/github/stars/StevenThuriot/TableStorage?style=social) 2024-06-01 + + 8 [Unflat](/docs/Unflat) [![Nuget](https://img.shields.io/nuget/dt/Unflat?label=Unflat)](https://www.nuget.org/packages/Unflat/) ![GitHub Repo stars](https://img.shields.io/github/stars/pstlnce/unflat?style=social) 2025-08-18 \ No newline at end of file diff --git a/v2/rscg_examples_site/docs/Categories/_PrimitiveDatabase.mdx b/v2/rscg_examples_site/docs/Categories/_PrimitiveDatabase.mdx index 926cd8846..853348eb5 100644 --- a/v2/rscg_examples_site/docs/Categories/_PrimitiveDatabase.mdx +++ b/v2/rscg_examples_site/docs/Categories/_PrimitiveDatabase.mdx @@ -6,13 +6,15 @@ 3 [EntityLengths.Generator](/docs/EntityLengths.Generator) [![Nuget](https://img.shields.io/nuget/dt/EntityLengths.Generator?label=EntityLengths.Generator)](https://www.nuget.org/packages/EntityLengths.Generator/) ![GitHub Repo stars](https://img.shields.io/github/stars/TarasKovalenko/EntityLengths.Generator?style=social) 2025-02-19 - 4 [Finch.Generators](/docs/Finch.Generators) [![Nuget](https://img.shields.io/nuget/dt/Finch.Generators?label=Finch.Generators)](https://www.nuget.org/packages/Finch.Generators/) ![GitHub Repo stars](https://img.shields.io/github/stars/ivmazurenko/finch?style=social) 2025-08-10 + 4 [Facet.Search](/docs/Facet.Search) [![Nuget](https://img.shields.io/nuget/dt/Facet.Search?label=Facet.Search)](https://www.nuget.org/packages/Facet.Search/) ![GitHub Repo stars](https://img.shields.io/github/stars/Tim-Maes/Facet.Search?style=social) 2025-12-17 - 5 [Gedaq](/docs/Gedaq) [![Nuget](https://img.shields.io/nuget/dt/Gedaq?label=Gedaq)](https://www.nuget.org/packages/Gedaq/) ![GitHub Repo stars](https://img.shields.io/github/stars/SoftStoneDevelop/Gedaq?style=social) 2023-07-29 + 5 [Finch.Generators](/docs/Finch.Generators) [![Nuget](https://img.shields.io/nuget/dt/Finch.Generators?label=Finch.Generators)](https://www.nuget.org/packages/Finch.Generators/) ![GitHub Repo stars](https://img.shields.io/github/stars/ivmazurenko/finch?style=social) 2025-08-10 - 6 [TableStorage](/docs/TableStorage) [![Nuget](https://img.shields.io/nuget/dt/TableStorage?label=TableStorage)](https://www.nuget.org/packages/TableStorage/) ![GitHub Repo stars](https://img.shields.io/github/stars/StevenThuriot/TableStorage?style=social) 2024-06-01 + 6 [Gedaq](/docs/Gedaq) [![Nuget](https://img.shields.io/nuget/dt/Gedaq?label=Gedaq)](https://www.nuget.org/packages/Gedaq/) ![GitHub Repo stars](https://img.shields.io/github/stars/SoftStoneDevelop/Gedaq?style=social) 2023-07-29 - 7 [Unflat](/docs/Unflat) [![Nuget](https://img.shields.io/nuget/dt/Unflat?label=Unflat)](https://www.nuget.org/packages/Unflat/) ![GitHub Repo stars](https://img.shields.io/github/stars/pstlnce/unflat?style=social) 2025-08-18 + 7 [TableStorage](/docs/TableStorage) [![Nuget](https://img.shields.io/nuget/dt/TableStorage?label=TableStorage)](https://www.nuget.org/packages/TableStorage/) ![GitHub Repo stars](https://img.shields.io/github/stars/StevenThuriot/TableStorage?style=social) 2024-06-01 + + 8 [Unflat](/docs/Unflat) [![Nuget](https://img.shields.io/nuget/dt/Unflat?label=Unflat)](https://www.nuget.org/packages/Unflat/) ![GitHub Repo stars](https://img.shields.io/github/stars/pstlnce/unflat?style=social) 2025-08-18 ### See category diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/Facet.Search.md b/v2/rscg_examples_site/docs/RSCG-Examples/Facet.Search.md new file mode 100644 index 000000000..1f1ef7db3 --- /dev/null +++ b/v2/rscg_examples_site/docs/RSCG-Examples/Facet.Search.md @@ -0,0 +1,722 @@ +--- +sidebar_position: 2500 +title: 250 - Facet.Search +description: Generating search from C# clasess and properties +slug: /Facet.Search +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import TOCInline from '@theme/TOCInline'; +import SameCategory from '../Categories/_PrimitiveDatabase.mdx'; + +# Facet.Search by Tim Maes + + + + +## NuGet / site data +[![Nuget](https://img.shields.io/nuget/dt/Facet.Search?label=Facet.Search)](https://www.nuget.org/packages/Facet.Search/) +[![GitHub last commit](https://img.shields.io/github/last-commit/Tim-Maes/Facet.Search?label=updated)](https://github.com/Tim-Maes/Facet.Search) +![GitHub Repo stars](https://img.shields.io/github/stars/Tim-Maes/Facet.Search?style=social) + +## Details + +### Info +:::info + +Name: **Facet.Search** + +Compile-time faceted search generation - attributes and source generators + +Author: Tim Maes + +NuGet: +*https://www.nuget.org/packages/Facet.Search/* + + +You can find more details at https://github.com/Tim-Maes/Facet.Search + +Source: https://github.com/Tim-Maes/Facet.Search + +::: + +### Author +:::note +Tim Maes +![Alt text](https://github.com/Tim-Maes.png) +::: + +### Original Readme +:::note + +# Facet.Search + +[![CI](https://github.com/Tim-Maes/Facet.Search/actions/workflows/build.yml/badge.svg)](https://github.com/Tim-Maes/Facet.Search/actions/workflows/build.yml) +[![Test](https://github.com/Tim-Maes/Facet.Search/actions/workflows/test.yml/badge.svg)](https://github.com/Tim-Maes/Facet.Search/actions/workflows/test.yml) +[![CD](https://github.com/Tim-Maes/Facet.Search/actions/workflows/release.yml/badge.svg)](https://github.com/Tim-Maes/Facet.Search/actions/workflows/release.yml) +[![NuGet](https://img.shields.io/nuget/v/Facet.Search.svg)](https://www.nuget.org/packages/Facet.Search) +[![Downloads](https://img.shields.io/nuget/dt/Facet.Search.svg)](https://www.nuget.org/packages/Facet.Search) +[![GitHub](https://img.shields.io/github/license/Tim-Maes/Facet.Search.svg)](https://github.com/Tim-Maes/Facet.Search/blob/main/LICENSE.txt) +[![Discord](https://img.shields.io/discord/1443287393825329223?color=%237289da&label=Discord&logo=discord&logoColor=%237289da&style=flat-square)](https://discord.gg/yGDBhGuNMB) + +**Compile-time faceted search generation for .NET**, Zero boilerplate, type-safe, and performant. + +Facet.Search uses source generators to automatically create search filter classes, LINQ extension methods, facet aggregations, and metadata from your domain models, all at compile time with no runtime overhead. + +## Features + +- **Zero Boilerplate** - Just add attributes to your models +- **Type-Safe** - All filters are compile-time checked +- **Performant** - Generated code is as efficient as hand-written +- **SQL Translated** - All filters execute on the database, not in memory +- **EF Core Integration** - [Extensions for EF Core](https://github.com/Tim-Maes/Facet.Search/tree/master/src/Facet.Search.EFCore) & multiple DB providers +- **Full-Text Search** - Built-in text search with multiple strategies +- **Facet Aggregations** - Automatic counting and range detection +- **Frontend Metadata** - Generate facet metadata for UI consumption + +## Installation + +```bash +dotnet add package Facet.Search +``` + +For Entity Framework Core integration: +```bash +dotnet add package Facet.Search.EFCore +``` + +## Quick Start + +### 1. Define Your Model + +```csharp +using Facet.Search; + +[FacetedSearch] +public class Product +{ + public int Id \{ get; set; } + + [FullTextSearch] + public string Name \{ get; set; \} = null!; + + [FullTextSearch(Weight = 0.5f)] + public string? Description \{ get; set; } + + [SearchFacet(Type = FacetType.Categorical, DisplayName = "Brand")] + public string Brand \{ get; set; \} = null!; + + [SearchFacet(Type = FacetType.Range, DisplayName = "Price")] + public decimal Price \{ get; set; } + + [SearchFacet(Type = FacetType.Boolean, DisplayName = "In Stock")] + public bool InStock \{ get; set; } + + [SearchFacet(Type = FacetType.DateRange, DisplayName = "Created Date")] + public DateTime CreatedAt \{ get; set; } +} +``` + +### 2. Use Generated Code + +The source generator automatically creates: +- `ProductSearchFilter` > Filter class with all facet properties +- `ProductSearchExtensions` > LINQ extension methods +- `ProductFacetAggregations` > Aggregation results +- `ProductSearchMetadata` > Facet metadata for frontends + +```csharp +using YourNamespace.Search; + +// Create a filter +var filter = new ProductSearchFilter +{ + Brand = ["Apple", "Samsung"], + MinPrice = 100m, + MaxPrice = 1000m, + InStock = true, + SearchText = "laptop" +}; + +// Apply to any IQueryable +var results = products.AsQueryable() + .ApplyFacetedSearch(filter) + .ToList(); + +// Get facet aggregations +var aggregations = products.AsQueryable().GetFacetAggregations(); +// aggregations.Brand = \{ "Apple": 5, "Samsung": 3, ... } +// aggregations.PriceMin = 99.99m +// aggregations.PriceMax = 2499.99m + +// Access metadata for UI +foreach (var facet in ProductSearchMetadata.Facets) +{ + Console.WriteLine($"{facet.DisplayName} ({facet.Type})"); +} +``` + +## How It Works with EF Core + +**All generated filters are translated to SQL**, no client-side evaluation for facet filters. + +| Filter Type | Generated Code | SQL Translation | +|-------------|---------------|-----------------| +| Categorical | `.Where(x => filter.Brand.Contains(x.Brand))` | `WHERE Brand IN ('Apple', 'Samsung')` | +| Range | `.Where(x => x.Price >= min && x.Price <= max)` | `WHERE Price >= @min AND Price <= @max` | +| Boolean | `.Where(x => x.InStock == true)` | `WHERE InStock = 1` | +| DateRange | `.Where(x => x.CreatedAt >= from)` | `WHERE CreatedAt >= @from` | +| Full-Text | `.Where(x => x.Name.Contains(term))` | `WHERE Name LIKE '%term%'` | + +### Full-Text Search Strategies + +By default, full-text search uses `LIKE '%term%'` which works with all databases but doesn't use full-text indexes. You can configure different strategies: + +```csharp +[FacetedSearch(FullTextStrategy = FullTextSearchStrategy.LinqContains)] // Default: LIKE '%term%' +[FacetedSearch(FullTextStrategy = FullTextSearchStrategy.SqlServerFreeText)] // SQL Server FREETEXT +[FacetedSearch(FullTextStrategy = FullTextSearchStrategy.PostgreSqlFullText)] // PostgreSQL tsvector +[FacetedSearch(FullTextStrategy = FullTextSearchStrategy.ClientSide)] // In-memory (use with caution) +public class Product \{ } +``` + +| Strategy | Database | Requires Index | Performance | +|----------|----------|----------------|-------------| +| `LinqContains` | All | No | Slow on large data | +| `EfLike` | All | No | Same as LinqContains | +| `SqlServerFreeText` | SQL Server | FULLTEXT index | Fast | +| `SqlServerContains` | SQL Server | FULLTEXT index | Fast | +| `PostgreSqlFullText` | PostgreSQL | GIN index | Fast | +| `ClientSide` | N/A | No | Loads to memory | + +## EF Core Integration + +Use the `Facet.Search.EFCore` package for async operations: + +```csharp +using Facet.Search.EFCore; + +// Async search execution +var results = await dbContext.Products + .ApplyFacetedSearch(filter) + .ExecuteSearchAsync(); + +// Paginated results +var pagedResult = await dbContext.Products + .ApplyFacetedSearch(filter) + .ToPagedResultAsync(page: 1, pageSize: 20); + +// pagedResult.Items, pagedResult.TotalCount, pagedResult.TotalPages + +// Async facet aggregation +var brandCounts = await dbContext.Products + .AggregateFacetAsync(p => p.Brand, limit: 10); + +// Get min/max range +var (minPrice, maxPrice) = await dbContext.Products + .GetRangeAsync(p => p.Price); +``` + +## Facet Types + +| Type | Description | Generated Filter Properties | +|------|-------------|----------------------------| +| `Categorical` | Discrete values (Brand, Category) | `string[]? PropertyName` | +| `Range` | Numeric ranges (Price, Rating) | `decimal? MinPropertyName`, `decimal? MaxPropertyName` | +| `Boolean` | True/false filters (InStock) | `bool? PropertyName` | +| `DateRange` | Date/time ranges | `DateTime? PropertyNameFrom`, `DateTime? PropertyNameTo` | +| `Hierarchical` | Nested categories | `string[]? PropertyName` | + +## Attributes Reference + +### `[FacetedSearch]` + +Marks a class for search generation. + +```csharp +[FacetedSearch( + FilterClassName = "CustomFilter", // Custom filter class name + GenerateAggregations = true, // Generate aggregation methods + GenerateMetadata = true, // Generate metadata class + Namespace = "Custom.Namespace", // Custom namespace for generated code + FullTextStrategy = FullTextSearchStrategy.LinqContains // Full-text search strategy +)] +public class Product \{ } +``` + +### `[SearchFacet]` + +Marks a property as a filterable facet. + +```csharp +[SearchFacet( + Type = FacetType.Categorical, // Facet type + DisplayName = "Product Brand", // UI display name + OrderBy = FacetOrder.Count, // Aggregation ordering (Count, Alphabetical) + Limit = 10, // Max aggregation values + DependsOn = "Category", // Dependent facet + IsHierarchical = false, // Hierarchical category + NavigationPath = "Category.Name", // For navigation properties + AutoInclude = true // Auto-include in EF Core queries +)] +public string Brand \{ get; set; } +``` + +### `[FullTextSearch]` + +Marks a property for full-text search. + +```csharp +[FullTextSearch( + Weight = 1.0f, // Search relevance weight (higher = more important) + CaseSensitive = false, // Case sensitivity + Behavior = TextSearchBehavior.Contains // Match behavior: Contains, StartsWith, EndsWith, Exact +)] +public string Name \{ get; set; } +``` + +### `[Searchable]` + +Marks a property as searchable but not a facet (useful for sorting). + +```csharp +[Searchable(Sortable = true)] +public int Rating \{ get; set; } +``` + +## Navigation Properties + +Filter on related entities by specifying the navigation path: + +```csharp +public class Product +{ + [SearchFacet( + Type = FacetType.Categorical, + DisplayName = "Category", + NavigationPath = "Category.Name" // Filter on Category.Name + )] + public Category Category \{ get; set; } +} +``` + +## Generated Code Location + +Generated files appear in your project's `obj` folder: +``` +obj/Debug/net8.0/generated/Facet.Search.Generators/ +> ProductSearchFilter.g.cs +> ProductSearchExtensions.g.cs +> ProductFacetAggregations.g.cs +> ProductSearchMetadata.g.cs +``` + +## Performance Tips + +1. **Use database indexes** on facet columns for fast filtering +2. **Use full-text indexes** for text search on large datasets +3. **Apply filters before pagination** to reduce data transfer +4. **Cache aggregations** if they don't change frequently +5. **Use `Limit` on categorical facets** to avoid loading all distinct values + +## Requirements + +- .NET Standard 2.0+ (for Facet.Search) +- .NET 10+ (for Facet.Search.EFCore) +- C# 9.0+ + +## License + +MIT License — see [LICENSE.txt](https://github.com/Tim-Maes/Facet.Search/LICENSE.md) for details. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## Related Projects + +- [Facet](https://github.com/Tim-Maes/Facet) — The Facet ecosystem + + +::: + +### About +:::note + +Generating search from C# clasess and properties + + +Integrating search in .NET applications + + +::: + +## How to use + +### Example (source csproj, source files) + + + + + +This is the CSharp Project that references **Facet.Search** +```xml showLineNumbers {11} + + + + Exe + net10.0 + enable + enable + + + + + + + + + true + $(BaseIntermediateOutputPath)\GX + + + +``` + + + + + + This is the use of **Facet.Search** in *Program.cs* + +```csharp showLineNumbers +using Microsoft.EntityFrameworkCore; +using SearchDemo; +using SearchDemo.Search; + +var filter = new PersonSearchFilter +{ + DOBFrom = new DateTime(1970, 1, 1), + DOBTo = new DateTime(1980, 12, 31), + IsActive = true, + MinSalary=1, + MaxSalary= 10, + SearchText= "Andrei" + + +}; + +MyAppContext cnt = new (); +var p= cnt.Person.ApplyFacetedSearch(filter); +var sql = p.ToQueryString(); +Console.WriteLine(sql); +``` + + + + + This is the use of **Facet.Search** in *Person.cs* + +```csharp showLineNumbers +using Facet.Search; +using Microsoft.EntityFrameworkCore; + +namespace SearchDemo; + +[FacetedSearch] +public class Person +{ + public int Id \{ get; set; } + [FullTextSearch] + public string Name \{ get; set; }= string.Empty; + + [SearchFacet(Type = FacetType.DateRange, DisplayName = "Date Of Birth")] + public DateTime DOB \{ get; set; } + [SearchFacet(Type = FacetType.Range, DisplayName = "SalaryRange")] + public int Salary \{ get; set; } + [SearchFacet(Type = FacetType.Boolean, DisplayName = "IsEmployee")] + public bool IsActive \{ get; set; } +} + +public class MyAppContext : DbContext +{ + public MyAppContext() + { + this.Person =this.Set(); + //fake + Person.Add(new Person() \{ + DOB= new DateTime(1970,4,16), + Id=1, + IsActive=true, + Name="Andrei Ignat", + Salary= 3 + }); + } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlServer("Server=myServerAddress;Database=myDataBase;Trusted_Connection=True;"); + base.OnConfiguring(optionsBuilder); + } + public DbSet Person \{ get; set; \} +} +``` + + + + +### Generated Files + +Those are taken from $(BaseIntermediateOutputPath)\GX + + + + +```csharp showLineNumbers +// +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SearchDemo.Search; + +/// +/// Aggregated facet results for Person. +/// +public class PersonFacetResults +{ + public int? SalaryMin \{ get; set; } + public int? SalaryMax \{ get; set; } + public int IsActiveTrueCount \{ get; set; } + public int IsActiveFalseCount \{ get; set; } +} + +public static class PersonFacetAggregationExtensions +{ + /// + /// Gets facet aggregations for Person. + /// + public static PersonFacetResults GetFacetAggregations( + this System.Linq.IQueryable query) + { + var results = new PersonFacetResults(); + + if (query.Any()) + { + results.SalaryMin = query.Min(x => x.Salary); + results.SalaryMax = query.Max(x => x.Salary); + } + + results.IsActiveTrueCount = query.Count(x => x.IsActive); + results.IsActiveFalseCount = query.Count(x => !x.IsActive); + + return results; + } +} + +``` + + + + +```csharp showLineNumbers +// +#nullable enable + +using System; +using System.Linq; + +namespace SearchDemo.Search; + +/// +/// Extension methods for searching Person. +/// +public static class PersonSearchExtensions +{ + /// + /// Applies faceted search filtering to a queryable of Person. + /// + /// + /// Full-text search strategy: LinqContains + /// All filters are translated to SQL and executed on the database server. + /// + public static System.Linq.IQueryable ApplyFacetedSearch( + this System.Linq.IQueryable query, + PersonSearchFilter filter) + { + if (filter == null) + return query; + + if (filter.DOBFrom.HasValue) + query = query.Where(x => x.DOB >= filter.DOBFrom.Value); + if (filter.DOBTo.HasValue) + query = query.Where(x => x.DOB <= filter.DOBTo.Value); + + if (filter.MinSalary.HasValue) + query = query.Where(x => x.Salary >= filter.MinSalary.Value); + if (filter.MaxSalary.HasValue) + query = query.Where(x => x.Salary <= filter.MaxSalary.Value); + + if (filter.IsActive.HasValue) + query = query.Where(x => x.IsActive == filter.IsActive.Value); + + // Full-text search + if (!string.IsNullOrWhiteSpace(filter.SearchText)) + { + // Uses LINQ Contains() -> translates to SQL LIKE '%term%' + var searchTerm = filter.SearchText.ToLower(); + query = query.Where(x => (x.Name != null && x.Name.ToLower().Contains(searchTerm))); + } + + return query; + } +} + +``` + + + + +```csharp showLineNumbers +// +#nullable enable + +namespace SearchDemo.Search; + +/// +/// Generated search filter for Person. +/// +public partial class PersonSearchFilter +{ + /// + /// Filter by Date Of Birth. + /// + public System.DateTime? DOBFrom \{ get; set; } + + /// + /// End date for DOB filter. + /// + public System.DateTime? DOBTo \{ get; set; } + + /// + /// Filter by SalaryRange. + /// + public int? MinSalary \{ get; set; } + + /// + /// Maximum Salary value. + /// + public int? MaxSalary \{ get; set; } + + /// + /// Filter by IsEmployee. + /// + public bool? IsActive \{ get; set; } + + /// + /// Full-text search query. + /// + public string? SearchText \{ get; set; } + +} + +``` + + + + +```csharp showLineNumbers +// +#nullable enable + +using System.Collections.Generic; + +namespace SearchDemo.Search; + +/// +/// Facet metadata for Person. +/// +public class PersonFacetMetadata +{ + public string Name \{ get; set; \} = null!; + public string PropertyName \{ get; set; \} = null!; + public string DisplayName \{ get; set; \} = null!; + public string Type \{ get; set; \} = null!; + public bool IsHierarchical \{ get; set; } + public string? DependsOn \{ get; set; } + public string OrderBy \{ get; set; \} = null!; + public int Limit \{ get; set; } + public string? RangeIntervals \{ get; set; } +} + +/// +/// Metadata about searchable facets for Person. +/// +public static class PersonSearchMetadata +{ + public static IReadOnlyList Facets \{ get; \} = new[] + { + new PersonFacetMetadata + { + Name = "DOB", + PropertyName = "DOB", + DisplayName = "Date Of Birth", + Type = "DateRange", + IsHierarchical = false, + OrderBy = "Count", + Limit = 0, + }, + new PersonFacetMetadata + { + Name = "Salary", + PropertyName = "Salary", + DisplayName = "SalaryRange", + Type = "Range", + IsHierarchical = false, + OrderBy = "Count", + Limit = 0, + }, + new PersonFacetMetadata + { + Name = "IsActive", + PropertyName = "IsActive", + DisplayName = "IsEmployee", + Type = "Boolean", + IsHierarchical = false, + OrderBy = "Count", + Limit = 0, + }, + }; +} + +``` + + + + +## Useful + +### Download Example (.NET C#) + +:::tip + +[Download Example project Facet.Search ](/sources/Facet.Search.zip) + +::: + + +### Share Facet.Search + + + +https://ignatandrei.github.io/RSCG_Examples/v2/docs/Facet.Search + + + diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/index.md b/v2/rscg_examples_site/docs/RSCG-Examples/index.md index 03e15a613..1a7327473 100644 --- a/v2/rscg_examples_site/docs/RSCG-Examples/index.md +++ b/v2/rscg_examples_site/docs/RSCG-Examples/index.md @@ -1,7 +1,7 @@ --- sidebar_position: 30 -title: 249 RSCG list by category -description: 249 RSCG list by category +title: 250 RSCG list by category +description: 250 RSCG list by category slug: /rscg-examples --- @@ -361,7 +361,7 @@ import DocCardList from '@theme/DocCardList'; ## Database
- Expand Database =>examples:7 + Expand Database =>examples:8 @@ -397,6 +397,11 @@ import DocCardList from '@theme/DocCardList'; [Unflat](/docs/Unflat) + + + +[Facet.Search](/docs/Facet.Search) +
@@ -1682,6 +1687,8 @@ flowchart LR; Database--> Unflat((Unflat)) + Database--> Facet.Search((Facet.Search)) + Decorator--> DecoratorGenerator((DecoratorGenerator)) DependencyInjection--> AutoRegisterInject((AutoRegisterInject)) diff --git a/v2/rscg_examples_site/docs/about.md b/v2/rscg_examples_site/docs/about.md index c55dec34d..41794d302 100644 --- a/v2/rscg_examples_site/docs/about.md +++ b/v2/rscg_examples_site/docs/about.md @@ -6,7 +6,7 @@ title: About ## Content You will find here code examples -of 249 Roslyn Source Code Generator (RSCG) +of 250 Roslyn Source Code Generator (RSCG) that can be useful for you. That means, you will write more elegant and concise code - even if the generators code is not always nice to look. ## Are those examples ready for production? diff --git a/v2/rscg_examples_site/docs/indexRSCG.md b/v2/rscg_examples_site/docs/indexRSCG.md index 444493781..9455bea57 100644 --- a/v2/rscg_examples_site/docs/indexRSCG.md +++ b/v2/rscg_examples_site/docs/indexRSCG.md @@ -7,9 +7,9 @@ slug: /List-of-RSCG import useBaseUrl from '@docusaurus/useBaseUrl'; -## 249 RSCG with examples in descending chronological order +## 250 RSCG with examples in descending chronological order -This is the list of 249 ( 16 from Microsoft) RSCG with examples +This is the list of 250 ( 16 from Microsoft) RSCG with examples [See by category](/docs/rscg-examples) [See as json](/exports/RSCG.json) [See as Excel](/exports/RSCG.xlsx) @@ -20,6 +20,7 @@ This is the list of 249 ( 16 from Microsoft) RSCG with examples | No | Name | Date | Category | | --------- | ----- | ---- | -------- | +|250| [Facet.Search by Tim Maes ](/docs/Facet.Search)|2025-12-17 => 17 December 2025 | [Database](/docs/Categories/Database) | |249| [Silhouette by Kevin Gosse ](/docs/Silhouette)|2025-12-16 => 16 December 2025 | [Profiler](/docs/Categories/Profiler) | |248| [docopt.net by Atif Aziz ](/docs/docopt.net)|2025-12-15 => 15 December 2025 | [CommandLine](/docs/Categories/CommandLine) | |247| [BlazorOcticons by Evgeniy K. ](/docs/BlazorOcticons)|2025-12-14 => 14 December 2025 | [Blazor](/docs/Categories/Blazor) | diff --git a/v2/rscg_examples_site/src/components/HomepageFeatures/index.js b/v2/rscg_examples_site/src/components/HomepageFeatures/index.js index b5871f230..8422c7d23 100644 --- a/v2/rscg_examples_site/src/components/HomepageFeatures/index.js +++ b/v2/rscg_examples_site/src/components/HomepageFeatures/index.js @@ -4,7 +4,7 @@ import styles from './styles.module.css'; const FeatureList = [ { -title: '249 Examples (16 from MSFT)', +title: '250 Examples (16 from MSFT)', Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( <> diff --git a/v2/rscg_examples_site/static/exports/RSCG.json b/v2/rscg_examples_site/static/exports/RSCG.json index ceed9e04e..7fea540f8 100644 --- a/v2/rscg_examples_site/static/exports/RSCG.json +++ b/v2/rscg_examples_site/static/exports/RSCG.json @@ -1993,6 +1993,14 @@ "Source": "https://github.com/kevingosse/Silhouette", "Category": "Profiler", "AddedOn": "2025-12-16T00:00:00" + }, + { + "Name": "Facet.Search", + "Link": "https://ignatandrei.github.io/RSCG_Examples/v2/docs/Facet.Search", + "NuGet": "https://www.nuget.org/packages/Facet.Search/", + "Source": "https://github.com/Tim-Maes/Facet.Search", + "Category": "Database", + "AddedOn": "2025-12-17T00:00:00" } ] } \ No newline at end of file diff --git a/v2/rscg_examples_site/static/exports/RSCG.xlsx b/v2/rscg_examples_site/static/exports/RSCG.xlsx index f95779c33..bca05a2b3 100644 Binary files a/v2/rscg_examples_site/static/exports/RSCG.xlsx and b/v2/rscg_examples_site/static/exports/RSCG.xlsx differ diff --git a/v2/rscg_examples_site/static/sources/Facet.Search.zip b/v2/rscg_examples_site/static/sources/Facet.Search.zip new file mode 100644 index 000000000..c6a4683c0 Binary files /dev/null and b/v2/rscg_examples_site/static/sources/Facet.Search.zip differ