Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches:
- master
env:
VERSION: 10.0.11
VERSION: 10.0.12
jobs:
build-and-deploy:
runs-on: ubuntu-latest
Expand Down
8 changes: 4 additions & 4 deletions Api.ApiClients.Audit/.claude/skills/nano-add-entity/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,22 +286,22 @@ public class <Entity>QueryCriteria : BaseQueryCriteria
{
var expressions = base.GetExpressions();

var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
var expression = expressions.First();

if (!string.IsNullOrEmpty(this.Name))
{
expression
.StartsWith(nameof(<Entity>.NameNormalized), this.Name.ToUpper());
}

expressions
.Add(expression);

return expressions;
}
}
```

- `base.GetExpressions()` already returns a list holding one `CriteriaExpression`, so add the conditions to
that first expression and return the list as it is. Never `Add` that same expression again, it would be
combined with itself. Only `Add` a new `CriteriaExpression` for a separate group that is AND-ed in.
- Only add filter properties for fields that make sense to search/filter by — don't
mechanically add one filter per scalar property on the entity.
- Every filter property must be `virtual` and nullable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,22 +286,22 @@ public class <Entity>QueryCriteria : BaseQueryCriteria
{
var expressions = base.GetExpressions();

var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
var expression = expressions.First();

if (!string.IsNullOrEmpty(this.Name))
{
expression
.StartsWith(nameof(<Entity>.NameNormalized), this.Name.ToUpper());
}

expressions
.Add(expression);

return expressions;
}
}
```

- `base.GetExpressions()` already returns a list holding one `CriteriaExpression`, so add the conditions to
that first expression and return the list as it is. Never `Add` that same expression again, it would be
combined with itself. Only `Add` a new `CriteriaExpression` for a separate group that is AND-ed in.
- Only add filter properties for fields that make sense to search/filter by — don't
mechanically add one filter per scalar property on the entity.
- Every filter property must be `virtual` and nullable.
Expand Down
19 changes: 16 additions & 3 deletions Api.ApiClients.Audit/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ public class MyUserController(ILogger<MyUserController> logger, MyApi myApi, MyO
| Attribute | Purpose |
| ------------- | ------------------------------------------------------------------------------------------------------------------ |
| `[Route(Order = n)]` | Positional route-template substitution (`{n}` placeholders in the action's route string, filled in `Order` sequence). |
| `[Query]` | Querystring parameter (scalar types); optional `Name` override. |
| `[Query]` | Querystring parameter: a scalar, a list of scalars, or an object (sent as `Name.Property=value`, such as `Paging.Number=2`); optional `Name` override. |
| `[Body]` | The JSON request body (one complex object). |
| `[Form]` | A `multipart/form-data` field — scalar, or `IFormFile`/`FileInfo`/`FileStream`/`Stream`/`NamedStream`; complex objects need `[FromFormBody]` server-side. Mutually exclusive with `[Body]`. |
| `[Header(Name=..., ValuePrefix=...)]` | An HTTP header key/value. |
Expand Down Expand Up @@ -297,6 +297,8 @@ public class SetMyFileRequest : BaseRequest
3. Add a method to the client, calling `InvokeAsync<TRequest>` (no response) or `InvokeAsync<TRequest, TResponse>`
(typed response — use `NamedStream` or `Stream` for file downloads).

**Paging a custom request**: give it one `[Query] public virtual Pagination? Paging { get; set; }` and have the client method set `Paging = paging`. It is sent as `Paging.Number`, `Paging.Count` and `Paging.Skip`, and the action binds it with `[FromQuery] Pagination? paging`, so `Skip` is kept. Never copy `Number` and `Count` into two scalar properties, that drops `Skip`.

**Controller resolution**: if a request doesn't set `this.Controller` explicitly in its constructor, it's
inferred as the pluralized `TResponse` type name (e.g. `IEnumerable<MyEntity>` → `MyEntities`). Set it explicitly
whenever the route doesn't naturally match the response type, or the request has no typed response at all.
Expand Down Expand Up @@ -2072,19 +2074,22 @@ public class MyEntityQueryCriteria : BaseQueryCriteria
public override IList<CriteriaExpression> GetExpressions()
{
var expressions = base.GetExpressions();
var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
var expression = expressions.First();

if (!string.IsNullOrEmpty(this.Name))
{
expression.StartsWith(nameof(MyEntity.Name), this.Name);
}

expressions.Add(expression);
return expressions;
}
}
```

`base.GetExpressions()` already returns a list holding one `CriteriaExpression`, so the conditions go on that first
expression and the list is returned as it is. Never `Add` that same expression again: the list is AND-ed, so it would
be combined with itself. Only `Add` a new `CriteriaExpression` for a separate group (see [Combining criteria](#combining-criteria--sequential-not-grouped)).

Built on the [DynamicExpression](https://github.com/vivet/DynamicExpression) library — criteria properties are
compiled into LINQ expressions against the entity, not hand-written `Where` clauses.

Expand Down Expand Up @@ -2212,6 +2217,14 @@ filter by a property on a **collection navigation**, wrap the nested property in
expression.GreaterThan("Orders[Total]", 100m); // x.Orders.Any(o => o.Total > 100)
```

A negated operation (`NotEqual`, `NotIn`, `NotContains`) on a collection path means **no element matches**, it compiles to
`!x.Orders.Any(...)` with the positive operation inside. Use it to leave out rows that have a related row, such as the
users who are not in an organization:

```csharp
expression.NotEqual("OrganizationScopes[OrganizationId]", organizationId); // !x.OrganizationScopes.Any(s => s.OrganizationId == organizationId)
```

##### Ordering and pagination

`Ordering` (`IQuery.Order`) — `By` (default `"Id"`, supports the same dotted-path navigation as criteria
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,14 @@ public override IList<CriteriaExpression> GetExpressions()
{
var expressions = base.GetExpressions();

var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
var expression = expressions.First();

if (!string.IsNullOrEmpty(this.Name))
{
expression
.StartsWith(nameof(Example.Name), this.Name);
}

expressions
.Add(expression);

return expressions;
}
}
8 changes: 4 additions & 4 deletions Api.ApiClients.Entity/.claude/skills/nano-add-entity/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,22 +286,22 @@ public class <Entity>QueryCriteria : BaseQueryCriteria
{
var expressions = base.GetExpressions();

var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
var expression = expressions.First();

if (!string.IsNullOrEmpty(this.Name))
{
expression
.StartsWith(nameof(<Entity>.NameNormalized), this.Name.ToUpper());
}

expressions
.Add(expression);

return expressions;
}
}
```

- `base.GetExpressions()` already returns a list holding one `CriteriaExpression`, so add the conditions to
that first expression and return the list as it is. Never `Add` that same expression again, it would be
combined with itself. Only `Add` a new `CriteriaExpression` for a separate group that is AND-ed in.
- Only add filter properties for fields that make sense to search/filter by — don't
mechanically add one filter per scalar property on the entity.
- Every filter property must be `virtual` and nullable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,22 +286,22 @@ public class <Entity>QueryCriteria : BaseQueryCriteria
{
var expressions = base.GetExpressions();

var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
var expression = expressions.First();

if (!string.IsNullOrEmpty(this.Name))
{
expression
.StartsWith(nameof(<Entity>.NameNormalized), this.Name.ToUpper());
}

expressions
.Add(expression);

return expressions;
}
}
```

- `base.GetExpressions()` already returns a list holding one `CriteriaExpression`, so add the conditions to
that first expression and return the list as it is. Never `Add` that same expression again, it would be
combined with itself. Only `Add` a new `CriteriaExpression` for a separate group that is AND-ed in.
- Only add filter properties for fields that make sense to search/filter by — don't
mechanically add one filter per scalar property on the entity.
- Every filter property must be `virtual` and nullable.
Expand Down
19 changes: 16 additions & 3 deletions Api.ApiClients.Entity/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ public class MyUserController(ILogger<MyUserController> logger, MyApi myApi, MyO
| Attribute | Purpose |
| ------------- | ------------------------------------------------------------------------------------------------------------------ |
| `[Route(Order = n)]` | Positional route-template substitution (`{n}` placeholders in the action's route string, filled in `Order` sequence). |
| `[Query]` | Querystring parameter (scalar types); optional `Name` override. |
| `[Query]` | Querystring parameter: a scalar, a list of scalars, or an object (sent as `Name.Property=value`, such as `Paging.Number=2`); optional `Name` override. |
| `[Body]` | The JSON request body (one complex object). |
| `[Form]` | A `multipart/form-data` field — scalar, or `IFormFile`/`FileInfo`/`FileStream`/`Stream`/`NamedStream`; complex objects need `[FromFormBody]` server-side. Mutually exclusive with `[Body]`. |
| `[Header(Name=..., ValuePrefix=...)]` | An HTTP header key/value. |
Expand Down Expand Up @@ -297,6 +297,8 @@ public class SetMyFileRequest : BaseRequest
3. Add a method to the client, calling `InvokeAsync<TRequest>` (no response) or `InvokeAsync<TRequest, TResponse>`
(typed response — use `NamedStream` or `Stream` for file downloads).

**Paging a custom request**: give it one `[Query] public virtual Pagination? Paging { get; set; }` and have the client method set `Paging = paging`. It is sent as `Paging.Number`, `Paging.Count` and `Paging.Skip`, and the action binds it with `[FromQuery] Pagination? paging`, so `Skip` is kept. Never copy `Number` and `Count` into two scalar properties, that drops `Skip`.

**Controller resolution**: if a request doesn't set `this.Controller` explicitly in its constructor, it's
inferred as the pluralized `TResponse` type name (e.g. `IEnumerable<MyEntity>` → `MyEntities`). Set it explicitly
whenever the route doesn't naturally match the response type, or the request has no typed response at all.
Expand Down Expand Up @@ -2072,19 +2074,22 @@ public class MyEntityQueryCriteria : BaseQueryCriteria
public override IList<CriteriaExpression> GetExpressions()
{
var expressions = base.GetExpressions();
var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
var expression = expressions.First();

if (!string.IsNullOrEmpty(this.Name))
{
expression.StartsWith(nameof(MyEntity.Name), this.Name);
}

expressions.Add(expression);
return expressions;
}
}
```

`base.GetExpressions()` already returns a list holding one `CriteriaExpression`, so the conditions go on that first
expression and the list is returned as it is. Never `Add` that same expression again: the list is AND-ed, so it would
be combined with itself. Only `Add` a new `CriteriaExpression` for a separate group (see [Combining criteria](#combining-criteria--sequential-not-grouped)).

Built on the [DynamicExpression](https://github.com/vivet/DynamicExpression) library — criteria properties are
compiled into LINQ expressions against the entity, not hand-written `Where` clauses.

Expand Down Expand Up @@ -2212,6 +2217,14 @@ filter by a property on a **collection navigation**, wrap the nested property in
expression.GreaterThan("Orders[Total]", 100m); // x.Orders.Any(o => o.Total > 100)
```

A negated operation (`NotEqual`, `NotIn`, `NotContains`) on a collection path means **no element matches**, it compiles to
`!x.Orders.Any(...)` with the positive operation inside. Use it to leave out rows that have a related row, such as the
users who are not in an organization:

```csharp
expression.NotEqual("OrganizationScopes[OrganizationId]", organizationId); // !x.OrganizationScopes.Any(s => s.OrganizationId == organizationId)
```

##### Ordering and pagination

`Ordering` (`IQuery.Order`) — `By` (default `"Id"`, supports the same dotted-path navigation as criteria
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,14 @@ public override IList<CriteriaExpression> GetExpressions()
{
var expressions = base.GetExpressions();

var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
var expression = expressions.First();

if (!string.IsNullOrEmpty(this.Name))
{
expression
.StartsWith(nameof(Example.Name), this.Name);
}

expressions
.Add(expression);

return expressions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -286,22 +286,22 @@ public class <Entity>QueryCriteria : BaseQueryCriteria
{
var expressions = base.GetExpressions();

var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
var expression = expressions.First();

if (!string.IsNullOrEmpty(this.Name))
{
expression
.StartsWith(nameof(<Entity>.NameNormalized), this.Name.ToUpper());
}

expressions
.Add(expression);

return expressions;
}
}
```

- `base.GetExpressions()` already returns a list holding one `CriteriaExpression`, so add the conditions to
that first expression and return the list as it is. Never `Add` that same expression again, it would be
combined with itself. Only `Add` a new `CriteriaExpression` for a separate group that is AND-ed in.
- Only add filter properties for fields that make sense to search/filter by — don't
mechanically add one filter per scalar property on the entity.
- Every filter property must be `virtual` and nullable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,22 +286,22 @@ public class <Entity>QueryCriteria : BaseQueryCriteria
{
var expressions = base.GetExpressions();

var expression = expressions.FirstOrDefault() ?? new CriteriaExpression();
var expression = expressions.First();

if (!string.IsNullOrEmpty(this.Name))
{
expression
.StartsWith(nameof(<Entity>.NameNormalized), this.Name.ToUpper());
}

expressions
.Add(expression);

return expressions;
}
}
```

- `base.GetExpressions()` already returns a list holding one `CriteriaExpression`, so add the conditions to
that first expression and return the list as it is. Never `Add` that same expression again, it would be
combined with itself. Only `Add` a new `CriteriaExpression` for a separate group that is AND-ed in.
- Only add filter properties for fields that make sense to search/filter by — don't
mechanically add one filter per scalar property on the entity.
- Every filter property must be `virtual` and nullable.
Expand Down
Loading
Loading