Compare commits
11 Commits
10.1.0-rc8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 55f1324286 | |||
|
|
b34bf874b4 | ||
|
|
c6de10b98b | ||
|
|
3945c1a158 | ||
| 7614f68512 | |||
|
|
fdee02c960 | ||
|
|
a4525bad6a | ||
|
|
3df094b9e7 | ||
| 6aece5a769 | |||
| b372805c4e | |||
| 89ccbe990f |
@ -1,50 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(dotnet clean:*)",
|
||||
"Bash(dotnet run)",
|
||||
"Bash(dotnet add:*)",
|
||||
"Bash(timeout 5 dotnet run:*)",
|
||||
"Bash(dotnet remove:*)",
|
||||
"Bash(netstat:*)",
|
||||
"Bash(findstr:*)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(taskkill:*)",
|
||||
"WebSearch",
|
||||
"Bash(dotnet tool install:*)",
|
||||
"Bash(protogen:*)",
|
||||
"Bash(timeout 15 dotnet run:*)",
|
||||
"Bash(where:*)",
|
||||
"Bash(timeout 30 dotnet run:*)",
|
||||
"Bash(timeout 60 dotnet run:*)",
|
||||
"Bash(timeout 120 dotnet run:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(timeout 3 cmd:*)",
|
||||
"Bash(timeout:*)",
|
||||
"Bash(tasklist:*)",
|
||||
"Bash(dotnet build:*)",
|
||||
"Bash(dotnet --list-sdks:*)",
|
||||
"Bash(dotnet sln:*)",
|
||||
"Bash(pkill:*)",
|
||||
"Bash(python3:*)",
|
||||
"Bash(grpcurl:*)",
|
||||
"Bash(lsof:*)",
|
||||
"Bash(xargs kill -9)",
|
||||
"Bash(dotnet run:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(dotnet pack:*)",
|
||||
"Bash(unzip:*)",
|
||||
"WebFetch(domain:andrewlock.net)",
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebFetch(domain:stackoverflow.com)",
|
||||
"WebFetch(domain:www.kenmuse.com)",
|
||||
"WebFetch(domain:blog.rsuter.com)",
|
||||
"WebFetch(domain:natemcmaster.com)",
|
||||
"WebFetch(domain:www.nuget.org)",
|
||||
"Bash(mkdir:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
96
.editorconfig
Normal file
96
.editorconfig
Normal file
@ -0,0 +1,96 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{csproj,props,targets,xml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{json,yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.proto]
|
||||
indent_size = 2
|
||||
|
||||
[*.cs]
|
||||
# Namespace
|
||||
csharp_style_namespace_declarations = file_scoped:warning
|
||||
|
||||
# Braces — Allman style
|
||||
csharp_new_line_before_open_brace = all
|
||||
|
||||
# Usings
|
||||
dotnet_sort_system_directives_first = true
|
||||
csharp_using_directive_placement = outside_namespace:warning
|
||||
|
||||
# var preferences — use var when type is apparent
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
|
||||
# Expression bodies — prefer for simple members
|
||||
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
|
||||
csharp_style_expression_bodied_constructors = false:suggestion
|
||||
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
|
||||
csharp_style_expression_bodied_properties = true:suggestion
|
||||
csharp_style_expression_bodied_accessors = true:suggestion
|
||||
csharp_style_expression_bodied_lambdas = true:suggestion
|
||||
|
||||
# Pattern matching
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
|
||||
# Null checking
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# Modifier preferences — exclude interface members (netstandard2.1 compat)
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
|
||||
|
||||
# Field naming — _camelCase for private fields
|
||||
dotnet_naming_rule.private_fields_should_be_camel_case.severity = warning
|
||||
dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields
|
||||
dotnet_naming_rule.private_fields_should_be_camel_case.style = camel_case_underscore
|
||||
|
||||
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, private_protected
|
||||
dotnet_naming_symbols.private_fields.required_modifiers =
|
||||
|
||||
dotnet_naming_style.camel_case_underscore.required_prefix = _
|
||||
dotnet_naming_style.camel_case_underscore.capitalization = camel_case
|
||||
|
||||
# Constants — PascalCase
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_symbols.constants.applicable_kinds = field
|
||||
dotnet_naming_symbols.constants.required_modifiers = const
|
||||
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
# Interfaces — I prefix
|
||||
dotnet_naming_rule.interfaces_should_begin_with_i.severity = warning
|
||||
dotnet_naming_rule.interfaces_should_begin_with_i.symbols = interfaces
|
||||
dotnet_naming_rule.interfaces_should_begin_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_symbols.interfaces.applicable_kinds = interface
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
# Async methods — Async suffix
|
||||
dotnet_naming_rule.async_methods_should_end_with_async.severity = suggestion
|
||||
dotnet_naming_rule.async_methods_should_end_with_async.symbols = async_methods
|
||||
dotnet_naming_rule.async_methods_should_end_with_async.style = ends_with_async
|
||||
|
||||
dotnet_naming_symbols.async_methods.applicable_kinds = method
|
||||
dotnet_naming_symbols.async_methods.required_modifiers = async
|
||||
|
||||
dotnet_naming_style.ends_with_async.required_suffix = Async
|
||||
dotnet_naming_style.ends_with_async.capitalization = pascal_case
|
||||
@ -1,6 +1,6 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
This file provides guidance to AI agents when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
|
||||
@ -4,6 +4,15 @@
|
||||
|
||||
Our implementation of query and command responsibility segregation (CQRS).
|
||||
|
||||
## Where This Fits
|
||||
|
||||
This is a backend framework of the [Svrnty Agent System](../README.md).
|
||||
|
||||
**Layer**: Framework
|
||||
**Depends on**: Nothing (standalone .NET framework)
|
||||
**Depended on by**: a-gent-app (backend services), flutter_cqrs_datasource (client)
|
||||
**Git**: [git.openharbor.io/svrnty/dotnet-cqrs](https://git.openharbor.io/svrnty/dotnet-cqrs)
|
||||
|
||||
## Getting Started
|
||||
|
||||
> Install nuget package to your awesome project.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.Abstractions.Attributes;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.Abstractions.Attributes;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Svrnty.CQRS.Abstractions.Attributes;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.Abstractions.Discovery;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Svrnty.CQRS.Abstractions.Discovery;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.Abstractions.Discovery;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Svrnty.CQRS.Abstractions.Attributes;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Svrnty.CQRS.Abstractions;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Svrnty.CQRS.Abstractions;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Svrnty.CQRS.Abstractions.Security;
|
||||
namespace Svrnty.CQRS.Abstractions.Security;
|
||||
|
||||
public enum AuthorizationResult
|
||||
{
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.Abstractions.Discovery;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
namespace Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
public interface IDynamicQueryParams<out TParams>
|
||||
where TParams : class
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
using Svrnty.CQRS.Abstractions;
|
||||
using Svrnty.CQRS.Abstractions.Attributes;
|
||||
using Svrnty.CQRS.Abstractions.Discovery;
|
||||
@ -14,7 +15,6 @@ using Svrnty.CQRS.Abstractions.Security;
|
||||
using Svrnty.CQRS.DynamicQuery;
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
using Svrnty.CQRS.DynamicQuery.Discover;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery.MinimalApi;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Pluralize.NET;
|
||||
using Svrnty.CQRS.Abstractions.Discovery;
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
using System;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery;
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery;
|
||||
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
using PoweredSoft.DynamicQuery;
|
||||
using PoweredSoft.DynamicQuery.Core;
|
||||
using Svrnty.CQRS.DynamicQuery.Abstractions;
|
||||
|
||||
namespace Svrnty.CQRS.DynamicQuery;
|
||||
|
||||
@ -16,7 +19,10 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
private readonly IQueryHandlerAsync _queryHandlerAsync;
|
||||
private readonly IEnumerable<IQueryableProvider<TSource>> _queryableProviders;
|
||||
private readonly IEnumerable<IAlterQueryableService<TSource, TDestination>> _alterQueryableServices;
|
||||
private readonly IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>> _dynamicQueryInterceptorProviders;
|
||||
|
||||
private readonly IEnumerable<IDynamicQueryInterceptorProvider<TSource, TDestination>>
|
||||
_dynamicQueryInterceptorProviders;
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DynamicQueryHandlerBase(IQueryHandlerAsync queryHandlerAsync,
|
||||
@ -32,7 +38,8 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
protected virtual Task<IQueryable<TSource>> GetQueryableAsync(IDynamicQuery query, CancellationToken cancellationToken = default)
|
||||
protected virtual Task<IQueryable<TSource>> GetQueryableAsync(IDynamicQuery query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_queryableProviders.Any())
|
||||
{
|
||||
@ -56,7 +63,8 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
yield return _serviceProvider.GetService(type) as IQueryInterceptor;
|
||||
}
|
||||
|
||||
protected async Task<IQueryExecutionResult<TDestination>> ProcessQueryAsync(IDynamicQuery query, CancellationToken cancellationToken = default)
|
||||
protected async Task<IQueryExecutionResult<TDestination>> ProcessQueryAsync(IDynamicQuery query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var source = await GetQueryableAsync(query, cancellationToken);
|
||||
source = await AlterSourceAsync(source, query, cancellationToken);
|
||||
@ -67,11 +75,13 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
_queryHandlerAsync.AddInterceptor(interceptor);
|
||||
|
||||
var criteria = CreateCriteriaFromQuery(query);
|
||||
var result = await _queryHandlerAsync.ExecuteAsync<TSource, TDestination>(source, criteria, options, cancellationToken);
|
||||
var result =
|
||||
await _queryHandlerAsync.ExecuteAsync<TSource, TDestination>(source, criteria, options, cancellationToken);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual async Task<IQueryable<TSource>> AlterSourceAsync(IQueryable<TSource> source, IDynamicQuery query, CancellationToken cancellationToken)
|
||||
protected virtual async Task<IQueryable<TSource>> AlterSourceAsync(IQueryable<TSource> source, IDynamicQuery query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var t in _alterQueryableServices)
|
||||
source = await t.AlterQueryableAsync(source, query, cancellationToken);
|
||||
@ -81,16 +91,94 @@ public abstract class DynamicQueryHandlerBase<TSource, TDestination>
|
||||
|
||||
protected virtual IQueryCriteria CreateCriteriaFromQuery(IDynamicQuery query)
|
||||
{
|
||||
var filters = query?.GetFilters() ?? new List<IFilter>();
|
||||
ConvertFilterValuesToPropertyTypes(filters);
|
||||
var criteria = new QueryCriteria
|
||||
{
|
||||
Page = query?.GetPage(),
|
||||
PageSize = query?.GetPageSize(),
|
||||
Filters = query?.GetFilters() ?? new List<IFilter>(),
|
||||
Filters = filters,
|
||||
Sorts = query?.GetSorts() ?? new List<ISort>(),
|
||||
Groups = query?.GetGroups() ?? new List<IGroup>(),
|
||||
Aggregates = query?.GetAggregates() ?? new List<IAggregate>()
|
||||
};
|
||||
return criteria;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts string filter values to the correct CLR type based on TSource property types.
|
||||
/// This handles the case where transport layers (e.g. gRPC) pass all values as strings,
|
||||
/// but PoweredSoft.DynamicLinq needs the actual type to build LINQ expressions.
|
||||
/// </summary>
|
||||
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2087",
|
||||
Justification = "TSource properties are preserved by EF Core and DynamicLinq usage")]
|
||||
private static void ConvertFilterValuesToPropertyTypes(List<IFilter> filters)
|
||||
{
|
||||
for (var i = filters.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var filter = filters[i];
|
||||
if (filter is SimpleFilter simpleFilter)
|
||||
{
|
||||
if (simpleFilter.Value == null)
|
||||
{
|
||||
filters.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (simpleFilter.Value is string strValue && !string.IsNullOrEmpty(strValue))
|
||||
{
|
||||
var propertyType = ResolvePropertyType(typeof(TSource), simpleFilter.Path);
|
||||
if (propertyType == null)
|
||||
continue;
|
||||
|
||||
var targetType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
|
||||
|
||||
if (targetType == typeof(DateTime))
|
||||
{
|
||||
if (DateTime.TryParse(strValue, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal,
|
||||
out var dt))
|
||||
{
|
||||
simpleFilter.Value = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
|
||||
}
|
||||
}
|
||||
else if (targetType == typeof(DateTimeOffset))
|
||||
{
|
||||
if (DateTimeOffset.TryParse(strValue, CultureInfo.InvariantCulture, DateTimeStyles.None,
|
||||
out var dto))
|
||||
{
|
||||
simpleFilter.Value = dto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (filter is CompositeFilter compositeFilter && compositeFilter.Filters != null)
|
||||
{
|
||||
ConvertFilterValuesToPropertyTypes(compositeFilter.Filters);
|
||||
}
|
||||
}
|
||||
|
||||
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070",
|
||||
Justification = "Property types are preserved by EF Core and DynamicLinq usage")]
|
||||
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075",
|
||||
Justification = "Nested property type resolution is inherently dynamic")]
|
||||
static Type? ResolvePropertyType(
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, string? path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return null;
|
||||
|
||||
var currentType = type;
|
||||
foreach (var part in path.Split('.'))
|
||||
{
|
||||
var property = currentType.GetProperty(part,
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||
if (property == null)
|
||||
return null;
|
||||
currentType = property.PropertyType;
|
||||
}
|
||||
|
||||
return currentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using PoweredSoft.Data.Core;
|
||||
@ -133,7 +133,7 @@ public static class ServiceCollectionExtensions
|
||||
where TParams : class
|
||||
where TService : class, IAlterQueryableService<TSourceAndTDestination, TSourceAndTDestination, TParams>
|
||||
{
|
||||
return services.AddTransient<IAlterQueryableService< TSourceAndTDestination, TSourceAndTDestination, TParams>, TService>();
|
||||
return services.AddTransient<IAlterQueryableService<TSourceAndTDestination, TSourceAndTDestination, TParams>, TService>();
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAlterQueryableWithParams<TSource, TDestination, TParams, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.Abstractions;
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Svrnty.CQRS.Grpc.Generators.Helpers;
|
||||
using Svrnty.CQRS.Grpc.Generators.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Svrnty.CQRS.Grpc.Generators
|
||||
namespace Svrnty.CQRS.Grpc.Generators;
|
||||
|
||||
[Generator]
|
||||
public class GrpcGenerator : IIncrementalGenerator
|
||||
{
|
||||
[Generator]
|
||||
public class GrpcGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
// Find all types that might be commands or queries from source
|
||||
@ -2864,7 +2864,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" Path = protoFilter.Path,");
|
||||
sb.AppendLine(" Type = ((PoweredSoft.DynamicQuery.Core.FilterType)protoFilter.Type).ToString(),");
|
||||
sb.AppendLine(" Value = protoFilter.Value");
|
||||
sb.AppendLine(" Value = protoFilter.Value,");
|
||||
sb.AppendLine(" And = true");
|
||||
sb.AppendLine(" };");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" // Handle nested AND filters");
|
||||
@ -2894,7 +2895,8 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" Path = pf.Path,");
|
||||
sb.AppendLine(" Type = ((PoweredSoft.DynamicQuery.Core.FilterType)pf.Type).ToString(),");
|
||||
sb.AppendLine(" Value = pf.Value");
|
||||
sb.AppendLine(" Value = pf.Value,");
|
||||
sb.AppendLine(" And = true");
|
||||
sb.AppendLine(" };");
|
||||
sb.AppendLine(" if (pf.And != null && pf.And.Count > 0)");
|
||||
sb.AppendLine(" {");
|
||||
@ -3356,5 +3358,4 @@ namespace Svrnty.CQRS.Grpc.Generators
|
||||
return string.Join("", parts.Select(p =>
|
||||
p.Length > 0 ? char.ToUpperInvariant(p[0]) + p.Substring(1).ToLowerInvariant() : ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Svrnty.CQRS.Grpc.Generators.Helpers
|
||||
namespace Svrnty.CQRS.Grpc.Generators.Helpers;
|
||||
|
||||
internal static class ProtoTypeMapper
|
||||
{
|
||||
internal static class ProtoTypeMapper
|
||||
{
|
||||
private static readonly Dictionary<string, string> TypeMap = new Dictionary<string, string>
|
||||
{
|
||||
// Primitives
|
||||
@ -98,5 +98,4 @@ namespace Svrnty.CQRS.Grpc.Generators.Helpers
|
||||
|
||||
return csharpType.Replace("?", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
namespace Svrnty.CQRS.Grpc.Generators.Models;
|
||||
|
||||
public class CommandInfo
|
||||
{
|
||||
public class CommandInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FullyQualifiedName { get; set; }
|
||||
public string Namespace { get; set; }
|
||||
@ -26,10 +26,10 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
ResultProperties = new List<PropertyInfo>();
|
||||
IsResultPrimitiveType = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PropertyInfo
|
||||
{
|
||||
public class PropertyInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string FullyQualifiedType { get; set; }
|
||||
@ -79,5 +79,4 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
IsElementComplexType = false;
|
||||
IsElementGuid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
namespace Svrnty.CQRS.Grpc.Generators.Models;
|
||||
|
||||
public class DynamicQueryInfo
|
||||
{
|
||||
public class DynamicQueryInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string SourceType { get; set; }
|
||||
public string SourceTypeFullyQualified { get; set; }
|
||||
@ -24,5 +24,4 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
QueryInterfaceName = string.Empty;
|
||||
HasParams = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
namespace Svrnty.CQRS.Grpc.Generators.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a discovered streaming notification type for proto/gRPC generation.
|
||||
/// </summary>
|
||||
public class NotificationInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a discovered streaming notification type for proto/gRPC generation.
|
||||
/// </summary>
|
||||
public class NotificationInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The notification type name (e.g., "InventoryChangeNotification").
|
||||
/// </summary>
|
||||
@ -46,5 +46,4 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
SubscriptionKeyInfo = new PropertyInfo();
|
||||
Properties = new List<PropertyInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
namespace Svrnty.CQRS.Grpc.Generators.Models;
|
||||
|
||||
public class QueryInfo
|
||||
{
|
||||
public class QueryInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FullyQualifiedName { get; set; }
|
||||
public string Namespace { get; set; }
|
||||
@ -26,5 +26,4 @@ namespace Svrnty.CQRS.Grpc.Generators.Models
|
||||
ResultProperties = new List<PropertyInfo>();
|
||||
IsResultPrimitiveType = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -413,17 +413,21 @@ internal class ProtoFileGenerator
|
||||
|
||||
private void GenerateComplexTypeMessage(INamedTypeSymbol? type)
|
||||
{
|
||||
if (type == null || _generatedMessages.Contains(type.Name))
|
||||
if (type == null)
|
||||
return;
|
||||
|
||||
var messageName = ProtoFileTypeMapper.GetProtoMessageName(type);
|
||||
if (_generatedMessages.Contains(messageName))
|
||||
return;
|
||||
|
||||
// Don't generate messages for system types or primitives
|
||||
if (type.ContainingNamespace?.ToString().StartsWith("System") == true)
|
||||
return;
|
||||
|
||||
_generatedMessages.Add(type.Name);
|
||||
_generatedMessages.Add(messageName);
|
||||
|
||||
_messagesBuilder.AppendLine($"// {type.Name} entity");
|
||||
_messagesBuilder.AppendLine($"message {type.Name} {{");
|
||||
_messagesBuilder.AppendLine($"// {messageName} entity");
|
||||
_messagesBuilder.AppendLine($"message {messageName} {{");
|
||||
|
||||
// Collect nested complex types to generate after closing this message
|
||||
var nestedComplexTypes = new List<INamedTypeSymbol>();
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Svrnty.CQRS.Grpc.Generators;
|
||||
@ -151,13 +152,27 @@ internal static class ProtoFileTypeMapper
|
||||
// Complex types (classes/records) become message types
|
||||
if (typeSymbol.TypeKind == TypeKind.Class || typeSymbol.TypeKind == TypeKind.Struct)
|
||||
{
|
||||
return typeName; // Reference the message type by name
|
||||
return GetProtoMessageName(typeSymbol); // Reference the message type by name (handles generics)
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return "string"; // Default to string for unknown types
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the proto message name for a type, handling generic types by qualifying
|
||||
/// with type arguments. e.g. Translation<FaqTranslationQueryItem> becomes TranslationOfFaqTranslationQueryItem.
|
||||
/// </summary>
|
||||
public static string GetProtoMessageName(ITypeSymbol typeSymbol)
|
||||
{
|
||||
if (typeSymbol is INamedTypeSymbol namedType && namedType.IsGenericType && namedType.TypeArguments.Length > 0)
|
||||
{
|
||||
var typeArgs = string.Join("And", namedType.TypeArguments.Select(t => GetProtoMessageName(t)));
|
||||
return $"{namedType.Name}Of{typeArgs}";
|
||||
}
|
||||
return typeSymbol.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts C# PascalCase property name to proto snake_case field name.
|
||||
/// Uses simple conversion: add underscore before each uppercase letter (except first).
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.Abstractions;
|
||||
using Svrnty.CQRS.Discovery;
|
||||
@ -43,7 +44,7 @@ public class CqrsBuilder
|
||||
/// <summary>
|
||||
/// Adds a command handler to the CQRS pipeline
|
||||
/// </summary>
|
||||
public CqrsBuilder AddCommand<TCommand, TCommandHandler>()
|
||||
public CqrsBuilder AddCommand<TCommand, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommandHandler>()
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand>
|
||||
{
|
||||
@ -54,7 +55,7 @@ public class CqrsBuilder
|
||||
/// <summary>
|
||||
/// Adds a command handler with result to the CQRS pipeline
|
||||
/// </summary>
|
||||
public CqrsBuilder AddCommand<TCommand, TResult, TCommandHandler>()
|
||||
public CqrsBuilder AddCommand<TCommand, TResult, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommandHandler>()
|
||||
where TCommand : class
|
||||
where TCommandHandler : class, ICommandHandler<TCommand, TResult>
|
||||
{
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Svrnty.CQRS.Abstractions.Discovery;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Svrnty.CQRS.Abstractions.Discovery;
|
||||
|
||||
@ -26,6 +26,10 @@
|
||||
<None Include="..\README.md" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Svrnty.CQRS.Abstractions\Svrnty.CQRS.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Svrnty.CQRS.Configuration;
|
||||
|
||||
namespace Svrnty.CQRS.MinimalApi;
|
||||
namespace Svrnty.CQRS;
|
||||
|
||||
public static class WebApplicationExtensions
|
||||
{
|
||||
@ -1,11 +1,11 @@
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Svrnty.CQRS;
|
||||
using Svrnty.CQRS.Abstractions;
|
||||
using Svrnty.CQRS.DynamicQuery;
|
||||
using Svrnty.CQRS.FluentValidation;
|
||||
using Svrnty.CQRS.Grpc;
|
||||
using Svrnty.Sample;
|
||||
using Svrnty.CQRS.MinimalApi;
|
||||
using Svrnty.CQRS.DynamicQuery;
|
||||
using Svrnty.CQRS.Abstractions;
|
||||
using Svrnty.Sample;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
|
||||
@ -21,6 +21,13 @@ service QueryService {
|
||||
|
||||
}
|
||||
|
||||
// DynamicQuery service for CQRS operations
|
||||
service DynamicQueryService {
|
||||
// Dynamic query for User
|
||||
rpc QueryUsers (DynamicQueryUsersRequest) returns (DynamicQueryUsersResponse);
|
||||
|
||||
}
|
||||
|
||||
// Request message for AddUserCommand
|
||||
message AddUserCommandRequest {
|
||||
string name = 1;
|
||||
@ -59,3 +66,46 @@ message User {
|
||||
string email = 3;
|
||||
}
|
||||
|
||||
// Dynamic query filter with AND/OR support
|
||||
message DynamicQueryFilter {
|
||||
string path = 1;
|
||||
int32 type = 2; // PoweredSoft.DynamicQuery.Core.FilterType
|
||||
string value = 3;
|
||||
repeated DynamicQueryFilter and = 4;
|
||||
repeated DynamicQueryFilter or = 5;
|
||||
}
|
||||
|
||||
// Dynamic query sort
|
||||
message DynamicQuerySort {
|
||||
string path = 1;
|
||||
bool ascending = 2;
|
||||
}
|
||||
|
||||
// Dynamic query group
|
||||
message DynamicQueryGroup {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
// Dynamic query aggregate
|
||||
message DynamicQueryAggregate {
|
||||
string path = 1;
|
||||
int32 type = 2; // PoweredSoft.DynamicQuery.Core.AggregateType
|
||||
}
|
||||
|
||||
// Dynamic query request for User
|
||||
message DynamicQueryUsersRequest {
|
||||
int32 page = 1;
|
||||
int32 page_size = 2;
|
||||
repeated DynamicQueryFilter filters = 3;
|
||||
repeated DynamicQuerySort sorts = 4;
|
||||
repeated DynamicQueryGroup groups = 5;
|
||||
repeated DynamicQueryAggregate aggregates = 6;
|
||||
}
|
||||
|
||||
// Dynamic query response for User
|
||||
message DynamicQueryUsersResponse {
|
||||
repeated User data = 1;
|
||||
int64 total_records = 2;
|
||||
int32 number_of_pages = 3;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using PoweredSoft.Data.Core;
|
||||
using System.Linq.Expressions;
|
||||
using PoweredSoft.Data.Core;
|
||||
|
||||
namespace Svrnty.Sample;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user