diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 7f98da7f1a..435201037d 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
- "version": "2023.3.0-eap08",
+ "version": "2024.1.4",
"commands": [
"jb"
]
@@ -15,16 +15,16 @@
]
},
"dotnet-reportgenerator-globaltool": {
- "version": "5.2.0",
+ "version": "5.3.6",
"commands": [
"reportgenerator"
]
},
"docfx": {
- "version": "2.74.0",
+ "version": "2.76.0",
"commands": [
"docfx"
]
}
}
-}
+}
\ No newline at end of file
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 4205b1ceec..e7d3ce2494 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -39,7 +39,7 @@ When you are creating an enhancement suggestion, please include as many details
- **Use a clear and descriptive title** for the issue to identify the suggestion.
- **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
-- **Provide specific examples to demonstrate the usage.** Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://docs.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks).
+- **Provide specific examples to demonstrate the usage.** Include copy/pasteable snippets which you use in those examples as [Markdown code blocks](https://docs.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks).
- **Describe the current behavior and explain which behavior you expected to see instead** and why.
- **Explain why this enhancement would be useful** to most users and isn't something that can or should be implemented in your API project directly.
- **Verify that your enhancement does not conflict** with the [JSON:API specification](https://jsonapi.org/).
@@ -56,7 +56,7 @@ Please follow these steps to have your contribution considered by the maintainer
- Follow all instructions in the template. Don't forget to add tests and update documentation.
- After you submit your pull request, verify that all status checks are passing. In release builds, all compiler warnings are treated as errors, so you should address them before push.
-We use [CSharpGuidelines](https://csharpcodingguidelines.com/) as our coding standard (with a few minor exceptions). Coding style is validated during PR build, where we inject an extra settings layer that promotes various suggestions to warning level. This ensures a high-quality codebase without interfering too much when editing code.
+We use [CSharpGuidelines](https://csharpcodingguidelines.com/) as our coding standard. Coding style is validated during PR build, where we inject an extra settings layer that promotes various IDE suggestions to warning level. This ensures a high-quality codebase without interfering too much while editing code.
You can run the following [PowerShell scripts](https://github.com/PowerShell/PowerShell/releases) locally:
- `pwsh ./inspectcode.ps1`: Scans the code for style violations and opens the result in your web browser.
- `pwsh ./cleanupcode.ps1 [branch-name-or-commit-hash]`: Reformats the codebase to match with our configured style, optionally only changed files since the specified branch (usually master).
@@ -86,13 +86,39 @@ public sealed class AppDbContext : DbContext
}
```
+### Pull request workflow
+
+Please follow the steps and guidelines below for a smooth experience.
+
+Authors:
+- When opening a new pull request, create it in **Draft** mode.
+- After you've completed the work *and* all checks are green, click the **Ready for review** button.
+ - If you have permissions to do so, ask a team member for review.
+- Once the review has started, don't force-push anymore.
+- When you've addressed feedback from a conversation, mark it with a thumbs-up or add a some text.
+- Don't close a conversation you didn't start. The creator closes it after verifying the concern has been addressed.
+- Apply suggestions in a batch, instead of individual commits (to minimize email notifications).
+- Re-request review when you're ready for another round.
+- If you want to clean up your commits before merge, let the reviewer know in time. This is optional.
+
+Reviewers:
+- If you're unable to review within a few days, let the author know what to expect.
+- Use **Start a review** instead of **Add single comment** (to minimize email notifications).
+- Consider to use suggestions (the ± button).
+- Don't close a conversation you didn't start. Close the ones you opened after verifying the concern has been addressed.
+- Once approved, use a merge commit only if all commits are clean. Otherwise, squash them into a single commit.
+ A commit is considered clean when:
+ - It is properly documented and covers all aspects of an isolated change (code, style, tests, docs).
+ - Checking out the commit results in a green build.
+ - Having this commit show up in the history is helpful (and can potentially be reverted).
+
## Creating a release (for maintainers)
- Verify documentation is up-to-date
-- Bump the package version in Directory.Build.props
+- Bump the package version in `Directory.Build.props`
- Create a GitHub release
-- Update https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb to consume the new version and release
-- Create a new branch in https://github.com/json-api-dotnet/MigrationGuide and update README.md in master
+- Update [JsonApiDotNetCore.MongoDb](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb) to consume the new version and release
+- Create a new branch in [MigrationGuide](https://github.com/json-api-dotnet/MigrationGuide) and update README.md in master, if major version change
## Backporting and hotfixes (for maintainers)
@@ -101,7 +127,7 @@ public sealed class AppDbContext : DbContext
git checkout tags/v2.5.1 -b release/2.5.2
```
- Cherrypick the merge commit: `git cherry-pick {git commit SHA}`
-- Bump the package version in Directory.Build.props
+- Bump the package version in `Directory.Build.props`
- Make any other compatibility, documentation, or tooling related changes
- Push the branch to origin and verify the build
- Once the build is verified, create a GitHub release, tagging the release branch
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4d7442a3c9..da0993c185 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -23,10 +23,6 @@ concurrency:
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
- # The Windows runner image has PostgreSQL pre-installed and sets the PGPASSWORD environment variable to "root".
- # This conflicts with the default password "postgres", which is used by ikalnytskyi/action-setup-postgres.
- # Because action-setup-postgres forgets to update the environment variable accordingly, we do so here.
- PGPASSWORD: "postgres"
jobs:
build-and-test:
@@ -39,47 +35,19 @@ jobs:
permissions:
contents: read
steps:
+ - name: Tune GitHub-hosted runner network
+ uses: smorimoto/tune-github-hosted-runner-network@v1
- name: Setup PostgreSQL
- uses: ikalnytskyi/action-setup-postgres@v4
+ uses: ikalnytskyi/action-setup-postgres@v6
with:
username: postgres
password: postgres
- name: Setup .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
- - name: Setup PowerShell (Ubuntu)
- if: matrix.os == 'ubuntu-latest'
- run: |
- dotnet tool install --global PowerShell
- - name: Find latest PowerShell version (Windows)
- if: matrix.os == 'windows-latest'
- shell: pwsh
- run: |
- $packageName = "powershell"
- $outputText = dotnet tool search $packageName --take 1
- $outputLine = ("" + $outputText)
- $indexOfVersionLine = $outputLine.IndexOf($packageName)
- $latestVersion = $outputLine.substring($indexOfVersionLine + $packageName.length).trim().split(" ")[0].trim()
-
- Write-Output "Found PowerShell version: $latestVersion"
- Write-Output "POWERSHELL_LATEST_VERSION=$latestVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- - name: Setup PowerShell (Windows)
- if: matrix.os == 'windows-latest'
- shell: cmd
- run: |
- set DOWNLOAD_LINK=https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_LATEST_VERSION%/PowerShell-%POWERSHELL_LATEST_VERSION%-win-x64.msi
- set OUTPUT_PATH=%RUNNER_TEMP%\PowerShell-%POWERSHELL_LATEST_VERSION%-win-x64.msi
- echo Downloading from: %DOWNLOAD_LINK% to: %OUTPUT_PATH%
- curl --location --output %OUTPUT_PATH% %DOWNLOAD_LINK%
- msiexec.exe /package %OUTPUT_PATH% /quiet USE_MU=1 ENABLE_MU=1 ADD_PATH=1 DISABLE_TELEMETRY=1
- - name: Setup PowerShell (macOS)
- if: matrix.os == 'macos-latest'
- run: |
- /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- brew install --cask powershell
- name: Show installed versions
shell: pwsh
run: |
@@ -105,6 +73,7 @@ jobs:
[xml]$xml = Get-Content Directory.Build.props
$configuredVersionPrefix = $xml.Project.PropertyGroup.JsonApiDotNetCoreVersionPrefix | Select-Object -First 1
+
if ($configuredVersionPrefix -ne $versionPrefix) {
Write-Error "Version prefix from git release tag '$versionPrefix' does not match version prefix '$configuredVersionPrefix' stored in Directory.Build.props."
# To recover from this:
@@ -128,11 +97,20 @@ jobs:
run: |
dotnet build --no-restore --configuration Release /p:VersionSuffix=$env:PACKAGE_VERSION_SUFFIX
- name: Test
+ env:
+ # Override log levels, to reduce logging output when running tests in ci-build.
+ Logging__LogLevel__Microsoft.Hosting.Lifetime: 'None'
+ Logging__LogLevel__Microsoft.AspNetCore.Hosting.Diagnostics: 'None'
+ Logging__LogLevel__Microsoft.Extensions.Hosting.Internal.Host: 'None'
+ Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command: 'None'
+ Logging__LogLevel__JsonApiDotNetCore: 'None'
run: |
dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --logger "GitHubActions;summary.includeSkippedTests=true"
- name: Upload coverage to codecov.io
if: matrix.os == 'ubuntu-latest'
- uses: codecov/codecov-action@v3
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+ uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
verbose: true
@@ -142,7 +120,7 @@ jobs:
dotnet pack --no-build --configuration Release --output $env:GITHUB_WORKSPACE/artifacts/packages /p:VersionSuffix=$env:PACKAGE_VERSION_SUFFIX
- name: Upload packages to artifacts
if: matrix.os == 'ubuntu-latest'
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: packages
path: artifacts/packages
@@ -165,7 +143,7 @@ jobs:
Copy-Item -Recurse home/assets/* _site/styles/
- name: Upload documentation to artifacts
if: matrix.os == 'ubuntu-latest'
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: documentation
path: docs/_site
@@ -180,8 +158,10 @@ jobs:
permissions:
contents: read
steps:
+ - name: Tune GitHub-hosted runner network
+ uses: smorimoto/tune-github-hosted-runner-network@v1
- name: Setup .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
@@ -196,7 +176,7 @@ jobs:
run: |
$inspectCodeOutputPath = Join-Path $env:RUNNER_TEMP 'jetbrains-inspectcode-results.xml'
Write-Output "INSPECT_CODE_OUTPUT_PATH=$inspectCodeOutputPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- dotnet jb inspectcode JsonApiDotNetCore.sln --build --dotnetcoresdk=$(dotnet --version) --output="$inspectCodeOutputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --properties:ContinuousIntegrationBuild=false --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal
+ dotnet jb inspectcode JsonApiDotNetCore.sln --build --dotnetcoresdk=$(dotnet --version) --output="$inspectCodeOutputPath" --format="xml" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --properties:ContinuousIntegrationBuild=false --properties:RunAnalyzers=false --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal
- name: Verify outcome
shell: pwsh
run: |
@@ -233,8 +213,10 @@ jobs:
permissions:
contents: read
steps:
+ - name: Tune GitHub-hosted runner network
+ uses: smorimoto/tune-github-hosted-runner-network@v1
- name: Setup .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
@@ -259,13 +241,13 @@ jobs:
$baseCommitHash = git rev-parse HEAD~1
Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash in pull request."
- dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff
+ dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --properties:RunAnalyzers=false --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff
- name: CleanupCode (on branch)
if: github.event_name == 'push' || github.event_name == 'release'
shell: pwsh
run: |
Write-Output "Running code cleanup on all files."
- dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN --fail-on-diff --print-diff
+ dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb --dotnetcoresdk=$(dotnet --version) --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --properties:RunAnalyzers=false --jb --verbosity=WARN --fail-on-diff --print-diff
publish:
timeout-minutes: 60
@@ -276,8 +258,10 @@ jobs:
packages: write
contents: write
steps:
+ - name: Tune GitHub-hosted runner network
+ uses: smorimoto/tune-github-hosted-runner-network@v1
- name: Download artifacts
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
- name: Publish to GitHub Packages
if: github.event_name == 'push' || github.event_name == 'release'
env:
@@ -288,7 +272,7 @@ jobs:
dotnet nuget push "$env:GITHUB_WORKSPACE/packages/*.nupkg" --api-key "$env:GITHUB_TOKEN" --source 'github'
- name: Publish documentation
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
- uses: peaceiris/actions-gh-pages@v3
+ uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 5b1868eae5..eb0375769e 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -24,7 +24,7 @@ jobs:
language: [ 'csharp' ]
steps:
- name: Setup .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
@@ -32,12 +32,12 @@ jobs:
- name: Git checkout
uses: actions/checkout@v4
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/deps-review.yml b/.github/workflows/deps-review.yml
index b9945082d5..b9d6d20fff 100644
--- a/.github/workflows/deps-review.yml
+++ b/.github/workflows/deps-review.yml
@@ -11,4 +11,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@v4
- name: 'Dependency Review'
- uses: actions/dependency-review-action@v3
+ uses: actions/dependency-review-action@v4
diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml
index 9225d9f816..8ce0acd5db 100644
--- a/.github/workflows/qodana.yml
+++ b/.github/workflows/qodana.yml
@@ -22,12 +22,12 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
fetch-depth: 0 # a full history is required for pull request analysis
- name: 'Qodana Scan'
- uses: JetBrains/qodana-action@v2023.2
+ uses: JetBrains/qodana-action@v2024.1
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
- name: Upload results to artifacts on failure
if: failure()
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: qodana_results
path: ${{ runner.temp }}/qodana/results
diff --git a/.gitignore b/.gitignore
index 85bd0f1080..c1757fc159 100644
--- a/.gitignore
+++ b/.gitignore
@@ -423,3 +423,6 @@ FodyWeavers.xsd
**/.idea/**/httpRequests/
**/.idea/**/dataSources/
!**/.idea/**/codeStyles/*
+
+# Workaround for https://github.com/microsoft/kiota/issues/4228
+kiota-lock.json
diff --git a/Directory.Build.props b/Directory.Build.props
index cdd3c5d232..860217f52e 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -27,6 +27,6 @@
-
-
- The goal of this library is to simplify the development of APIs that leverage the full range of features provided by the JSON:API specification. + The goal of this library is to simplify the development of APIs that leverage the full range of features + provided by the JSON:API specification. You just need to focus on defining the resources and implementing your custom business logic.
We strive to eliminate as much boilerplate as possible by offering out-of-the-box features such as sorting, filtering and pagination.
The following features are supported, from HTTP all the way down to the database
Perform compound filtering using the filter
query string parameter
Order resources on one or multiple attributes using the sort
query string parameter
Leverage the benefits of paginated resources with the page
query string parameter
Side-load related resources of nested relationships using the include
query string parameter
Configure permissions, such as view/create/change/sort/filter of attributes and relationships
+Configure permissions, such as viewing, creating, modifying, sorting and filtering of attributes and relationships
Validate incoming requests using built-in ASP.NET Core ModelState
validation, which works seamlessly with partial updates
Validate incoming requests using built-in ASP.NET Model Validation, which works seamlessly with partial updates
Use various extensibility points to intercept and run custom code, besides just model annotations
#nullable enable
@@ -179,16 +211,16 @@ Resource
-
+
Request
GET /articles?filter=contains(summary,'web')&sort=-lastModifiedAt&fields[articles]=title,summary&include=author HTTP/1.1
-
+
-
+
Response
{
@@ -259,15 +291,15 @@ Response
Sponsors
-
-
+
+
-
-
+
+
diff --git a/docs/request-examples/index.md b/docs/request-examples/index.md
index c34b3d713a..614aa4814f 100644
--- a/docs/request-examples/index.md
+++ b/docs/request-examples/index.md
@@ -1,3 +1,7 @@
+---
+_disableToc: true
+---
+
# Example requests
These requests have been generated against the "GettingStarted" application and are updated on every deployment.
diff --git a/docs/request-examples/toc.md b/docs/request-examples/toc.md
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs/usage/extensibility/controllers.md b/docs/usage/extensibility/controllers.md
index 7e54d3fb9c..68e1d86ea3 100644
--- a/docs/usage/extensibility/controllers.md
+++ b/docs/usage/extensibility/controllers.md
@@ -2,6 +2,8 @@
To expose API endpoints, ASP.NET controllers need to be defined.
+## Auto-generated controllers
+
_since v5_
Controllers are auto-generated (using [source generators](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview)) when you add `[Resource]` on your model class:
@@ -14,7 +16,12 @@ public class Article : Identifiable
}
```
-## Resource Access Control
+> [!NOTE]
+> Auto-generated controllers are convenient to get started, but may not work as expected with certain customizations.
+> For example, when model classes are defined in a separate project, the controllers are generated in that project as well, which is probably not what you want.
+> In such cases, it's perfectly fine to use [explicit controllers](#explicit-controllers) instead.
+
+### Resource Access Control
It is often desirable to limit which endpoints are exposed on your controller.
A subset can be specified too:
@@ -52,7 +59,7 @@ DELETE http://localhost:14140/articles/1 HTTP/1.1
}
```
-## Augmenting controllers
+### Augmenting controllers
Auto-generated controllers can easily be augmented because they are partial classes. For example:
@@ -91,9 +98,9 @@ partial class ArticlesController
In case you don't want to use auto-generated controllers and define them yourself (see below), remove
`[Resource]` from your models or use `[Resource(GenerateControllerEndpoints = JsonApiEndpoints.None)]`.
-## Earlier versions
+## Explicit controllers
-In earlier versions of JsonApiDotNetCore, you needed to create controllers that inherit from `JsonApiController`. For example:
+To define your own controller class, inherit from `JsonApiController`. For example:
```c#
public class ArticlesController : JsonApiController
diff --git a/docs/usage/writing/bulk-batch-operations.md b/docs/usage/writing/bulk-batch-operations.md
index 1ac35fd3fc..5756755b51 100644
--- a/docs/usage/writing/bulk-batch-operations.md
+++ b/docs/usage/writing/bulk-batch-operations.md
@@ -19,13 +19,24 @@ public sealed class OperationsController : JsonApiOperationsController
{
public OperationsController(IJsonApiOptions options, IResourceGraph resourceGraph,
ILoggerFactory loggerFactory, IOperationsProcessor processor,
- IJsonApiRequest request, ITargetedFields targetedFields)
- : base(options, resourceGraph, loggerFactory, processor, request, targetedFields)
+ IJsonApiRequest request, ITargetedFields targetedFields,
+ IAtomicOperationFilter operationFilter)
+ : base(options, resourceGraph, loggerFactory, processor, request, targetedFields,
+ operationFilter)
{
}
}
```
+> [!IMPORTANT]
+> Since v5.6.0, the set of exposed operations is based on
+> [`GenerateControllerEndpoints` usage](~/usage/extensibility/controllers.md#resource-access-control).
+> Earlier versions always exposed all operations for all resource types.
+> If you're using [explicit controllers](~/usage/extensibility/controllers.md#explicit-controllers),
+> register and implement your own
+> [`IAtomicOperationFilter`](~/api/JsonApiDotNetCore.AtomicOperations.IAtomicOperationFilter.yml)
+> to indicate which operations to expose.
+
You'll need to send the next Content-Type in a POST request for operations:
```
diff --git a/docs/usage/writing/creating.md b/docs/usage/writing/creating.md
index ba0a21d52b..8cc0c03e49 100644
--- a/docs/usage/writing/creating.md
+++ b/docs/usage/writing/creating.md
@@ -16,8 +16,8 @@ POST /articles HTTP/1.1
}
```
-When using client-generated IDs and only attributes from the request have changed, the server returns `204 No Content`.
-Otherwise, the server returns `200 OK`, along with the updated resource and its newly assigned ID.
+When using client-generated IDs and all attributes of the created resource are the same as in the request, the server
+returns `204 No Content`. Otherwise, the server returns `201 Created`, along with the stored attributes and its newly assigned ID.
In both cases, a `Location` header is returned that contains the URL to the new resource.
diff --git a/docs/usage/writing/updating.md b/docs/usage/writing/updating.md
index ea27e1a220..30e1b4fa7d 100644
--- a/docs/usage/writing/updating.md
+++ b/docs/usage/writing/updating.md
@@ -5,7 +5,7 @@
To modify the attributes of a single resource, send a PATCH request. The next example changes the article caption:
```http
-PATCH /articles HTTP/1.1
+PATCH /articles/1 HTTP/1.1
{
"data": {
diff --git a/inspectcode.ps1 b/inspectcode.ps1
index 14c3eb1736..21e96eac67 100644
--- a/inspectcode.ps1
+++ b/inspectcode.ps1
@@ -10,7 +10,7 @@ if ($LastExitCode -ne 0) {
$outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml')
$resultPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.html')
-dotnet jb inspectcode JsonApiDotNetCore.sln --dotnetcoresdk=$(dotnet --version) --build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal
+dotnet jb inspectcode JsonApiDotNetCore.sln --dotnetcoresdk=$(dotnet --version) --build --output="$outputPath" --format="xml" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --properties:RunAnalyzers=false --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal
if ($LastExitCode -ne 0) {
throw "Code inspection failed with exit code $LastExitCode"
diff --git a/logo.png b/logo.png
deleted file mode 100644
index 78f1acd521..0000000000
Binary files a/logo.png and /dev/null differ
diff --git a/package-icon.png b/package-icon.png
new file mode 100644
index 0000000000..f95eb770e8
Binary files /dev/null and b/package-icon.png differ
diff --git a/package-versions.props b/package-versions.props
index dd842f59f6..9ba0b34bbb 100644
--- a/package-versions.props
+++ b/package-versions.props
@@ -7,16 +7,16 @@
0.13.*
- 34.0.*
- 4.7.*
+ 35.5.*
+ 4.10.*
6.0.*
2.1.*
6.12.*
2.3.*
- 1.3.*
+ 2.0.*
8.0.*
- 17.8.*
- 2.5.*
+ 17.10.*
+ 2.8.*
@@ -25,7 +25,7 @@
8.0.*
- 8.0.*-*
+ 8.0.*
$(AspNetCoreVersion)
diff --git a/src/Examples/DapperExample/Controllers/OperationsController.cs b/src/Examples/DapperExample/Controllers/OperationsController.cs
index 979e6c9cd7..2b9daf492f 100644
--- a/src/Examples/DapperExample/Controllers/OperationsController.cs
+++ b/src/Examples/DapperExample/Controllers/OperationsController.cs
@@ -6,11 +6,7 @@
namespace DapperExample.Controllers;
-public sealed class OperationsController : JsonApiOperationsController
-{
- public OperationsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor,
- IJsonApiRequest request, ITargetedFields targetedFields)
- : base(options, resourceGraph, loggerFactory, processor, request, targetedFields)
- {
- }
-}
+public sealed class OperationsController(
+ IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request,
+ ITargetedFields targetedFields, IAtomicOperationFilter operationFilter) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor,
+ request, targetedFields, operationFilter);
diff --git a/src/Examples/DapperExample/Data/RotatingList.cs b/src/Examples/DapperExample/Data/RotatingList.cs
index 67c19bea4a..278c34140a 100644
--- a/src/Examples/DapperExample/Data/RotatingList.cs
+++ b/src/Examples/DapperExample/Data/RotatingList.cs
@@ -16,16 +16,11 @@ public static RotatingList Create(int count, Func createElement)
}
}
-internal sealed class RotatingList
+internal sealed class RotatingList(IList elements)
{
private int _index = -1;
- public IList Elements { get; }
-
- public RotatingList(IList elements)
- {
- Elements = elements;
- }
+ public IList Elements { get; } = elements;
public T GetNext()
{
diff --git a/src/Examples/DapperExample/Repositories/DapperRepository.cs b/src/Examples/DapperExample/Repositories/DapperRepository.cs
index c263ad7767..0c54b34353 100644
--- a/src/Examples/DapperExample/Repositories/DapperRepository.cs
+++ b/src/Examples/DapperExample/Repositories/DapperRepository.cs
@@ -1,4 +1,5 @@
using System.Data.Common;
+using System.Diagnostics.CodeAnalysis;
using Dapper;
using DapperExample.AtomicOperations;
using DapperExample.TranslationToSql;
@@ -161,7 +162,7 @@ public async Task> GetAsync(QueryLayer queryLayer
}
///
- public Task CountAsync(FilterExpression? filter, CancellationToken cancellationToken)
+ public async Task CountAsync(FilterExpression? filter, CancellationToken cancellationToken)
{
var queryLayer = new QueryLayer(ResourceType)
{
@@ -173,11 +174,11 @@ public Task CountAsync(FilterExpression? filter, CancellationToken cancella
CommandDefinition sqlCommand = _dapperFacade.GetSqlCommand(selectNode, cancellationToken);
LogSqlCommand(sqlCommand);
- return ExecuteQueryAsync(connection => connection.ExecuteScalarAsync(sqlCommand), cancellationToken);
+ return await ExecuteQueryAsync(async connection => await connection.ExecuteScalarAsync(sqlCommand), cancellationToken);
}
///
- public Task GetForCreateAsync(Type resourceClrType, TId id, CancellationToken cancellationToken)
+ public Task GetForCreateAsync(Type resourceClrType, [DisallowNull] TId id, CancellationToken cancellationToken)
{
ArgumentGuard.NotNull(resourceClrType);
@@ -355,7 +356,7 @@ await ExecuteInTransactionAsync(async transaction =>
}
///
- public async Task DeleteAsync(TResource? resourceFromDatabase, TId id, CancellationToken cancellationToken)
+ public async Task DeleteAsync(TResource? resourceFromDatabase, [DisallowNull] TId id, CancellationToken cancellationToken)
{
TResource placeholderResource = resourceFromDatabase ?? _resourceFactory.CreateInstance();
placeholderResource.Id = id;
@@ -451,7 +452,7 @@ await ExecuteInTransactionAsync(async transaction =>
}
///
- public async Task AddToToManyRelationshipAsync(TResource? leftResource, TId leftId, ISet rightResourceIds,
+ public async Task AddToToManyRelationshipAsync(TResource? leftResource, [DisallowNull] TId leftId, ISet rightResourceIds,
CancellationToken cancellationToken)
{
ArgumentGuard.NotNull(rightResourceIds);
diff --git a/src/Examples/DapperExample/Repositories/ResultSetMapper.cs b/src/Examples/DapperExample/Repositories/ResultSetMapper.cs
index 61421d7331..4352b6c552 100644
--- a/src/Examples/DapperExample/Repositories/ResultSetMapper.cs
+++ b/src/Examples/DapperExample/Repositories/ResultSetMapper.cs
@@ -166,14 +166,9 @@ public IReadOnlyCollection GetResources()
return _primaryResourcesInOrder.DistinctBy(resource => resource.Id).ToList();
}
- private sealed class IncludeElementWalker
+ private sealed class IncludeElementWalker(IncludeExpression include)
{
- private readonly IncludeExpression _include;
-
- public IncludeElementWalker(IncludeExpression include)
- {
- _include = include;
- }
+ private readonly IncludeExpression _include = include;
public IEnumerable BreadthFirstEnumerate()
{
diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/DeleteOneToOneStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/DeleteOneToOneStatementBuilder.cs
index 5a1293d41b..3ea368b299 100644
--- a/src/Examples/DapperExample/TranslationToSql/Builders/DeleteOneToOneStatementBuilder.cs
+++ b/src/Examples/DapperExample/TranslationToSql/Builders/DeleteOneToOneStatementBuilder.cs
@@ -6,13 +6,8 @@
namespace DapperExample.TranslationToSql.Builders;
-internal sealed class DeleteOneToOneStatementBuilder : StatementBuilder
+internal sealed class DeleteOneToOneStatementBuilder(IDataModelService dataModelService) : StatementBuilder(dataModelService)
{
- public DeleteOneToOneStatementBuilder(IDataModelService dataModelService)
- : base(dataModelService)
- {
- }
-
public DeleteNode Build(ResourceType resourceType, string whereColumnName, object? whereValue)
{
ArgumentGuard.NotNull(resourceType);
diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/DeleteResourceStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/DeleteResourceStatementBuilder.cs
index 41794e8883..1910075a8c 100644
--- a/src/Examples/DapperExample/TranslationToSql/Builders/DeleteResourceStatementBuilder.cs
+++ b/src/Examples/DapperExample/TranslationToSql/Builders/DeleteResourceStatementBuilder.cs
@@ -6,13 +6,8 @@
namespace DapperExample.TranslationToSql.Builders;
-internal sealed class DeleteResourceStatementBuilder : StatementBuilder
+internal sealed class DeleteResourceStatementBuilder(IDataModelService dataModelService) : StatementBuilder(dataModelService)
{
- public DeleteResourceStatementBuilder(IDataModelService dataModelService)
- : base(dataModelService)
- {
- }
-
public DeleteNode Build(ResourceType resourceType, params object[] idValues)
{
ArgumentGuard.NotNull(resourceType);
diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/InsertStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/InsertStatementBuilder.cs
index 7e444b45a1..5f8374df65 100644
--- a/src/Examples/DapperExample/TranslationToSql/Builders/InsertStatementBuilder.cs
+++ b/src/Examples/DapperExample/TranslationToSql/Builders/InsertStatementBuilder.cs
@@ -6,13 +6,8 @@
namespace DapperExample.TranslationToSql.Builders;
-internal sealed class InsertStatementBuilder : StatementBuilder
+internal sealed class InsertStatementBuilder(IDataModelService dataModelService) : StatementBuilder(dataModelService)
{
- public InsertStatementBuilder(IDataModelService dataModelService)
- : base(dataModelService)
- {
- }
-
public InsertNode Build(ResourceType resourceType, IReadOnlyDictionary columnsToSet)
{
ArgumentGuard.NotNull(resourceType);
diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/SqlQueryBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/SqlQueryBuilder.cs
index 3e3d48eb10..a082d3ecad 100644
--- a/src/Examples/DapperExample/TranslationToSql/Builders/SqlQueryBuilder.cs
+++ b/src/Examples/DapperExample/TranslationToSql/Builders/SqlQueryBuilder.cs
@@ -8,7 +8,7 @@ namespace DapperExample.TranslationToSql.Builders;
///
/// Converts s into SQL text.
///
-internal sealed class SqlQueryBuilder : SqlTreeNodeVisitor
+internal sealed class SqlQueryBuilder(DatabaseProvider databaseProvider) : SqlTreeNodeVisitor
{
private static readonly char[] SpecialCharactersInLikeDefault =
[
@@ -26,7 +26,7 @@ internal sealed class SqlQueryBuilder : SqlTreeNodeVisitor _parametersByName = [];
private int _indentDepth;
@@ -35,11 +35,6 @@ internal sealed class SqlQueryBuilder : SqlTreeNodeVisitor Parameters => _parametersByName.Values.ToDictionary(parameter => parameter.Name, parameter => parameter.Value);
- public SqlQueryBuilder(DatabaseProvider databaseProvider)
- {
- _databaseProvider = databaseProvider;
- }
-
public string GetCommand(SqlTreeNode node)
{
ArgumentGuard.NotNull(node);
@@ -488,14 +483,9 @@ private IDisposable Indent()
return new RevertIndentOnDispose(this);
}
- private sealed class RevertIndentOnDispose : IDisposable
+ private sealed class RevertIndentOnDispose(SqlQueryBuilder owner) : IDisposable
{
- private readonly SqlQueryBuilder _owner;
-
- public RevertIndentOnDispose(SqlQueryBuilder owner)
- {
- _owner = owner;
- }
+ private readonly SqlQueryBuilder _owner = owner;
public void Dispose()
{
diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/UpdateClearOneToOneStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/UpdateClearOneToOneStatementBuilder.cs
index 4ccc5fec9a..712561528d 100644
--- a/src/Examples/DapperExample/TranslationToSql/Builders/UpdateClearOneToOneStatementBuilder.cs
+++ b/src/Examples/DapperExample/TranslationToSql/Builders/UpdateClearOneToOneStatementBuilder.cs
@@ -6,13 +6,8 @@
namespace DapperExample.TranslationToSql.Builders;
-internal sealed class UpdateClearOneToOneStatementBuilder : StatementBuilder
+internal sealed class UpdateClearOneToOneStatementBuilder(IDataModelService dataModelService) : StatementBuilder(dataModelService)
{
- public UpdateClearOneToOneStatementBuilder(IDataModelService dataModelService)
- : base(dataModelService)
- {
- }
-
public UpdateNode Build(ResourceType resourceType, string setColumnName, string whereColumnName, object? whereValue)
{
ArgumentGuard.NotNull(resourceType);
diff --git a/src/Examples/DapperExample/TranslationToSql/Builders/UpdateResourceStatementBuilder.cs b/src/Examples/DapperExample/TranslationToSql/Builders/UpdateResourceStatementBuilder.cs
index 62fc3b7e20..a279ad45e5 100644
--- a/src/Examples/DapperExample/TranslationToSql/Builders/UpdateResourceStatementBuilder.cs
+++ b/src/Examples/DapperExample/TranslationToSql/Builders/UpdateResourceStatementBuilder.cs
@@ -6,13 +6,8 @@
namespace DapperExample.TranslationToSql.Builders;
-internal sealed class UpdateResourceStatementBuilder : StatementBuilder
+internal sealed class UpdateResourceStatementBuilder(IDataModelService dataModelService) : StatementBuilder(dataModelService)
{
- public UpdateResourceStatementBuilder(IDataModelService dataModelService)
- : base(dataModelService)
- {
- }
-
public UpdateNode Build(ResourceType resourceType, IReadOnlyDictionary columnsToUpdate, params object[] idValues)
{
ArgumentGuard.NotNull(resourceType);
diff --git a/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs b/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs
index 0f030debdb..711ad8517c 100644
--- a/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs
+++ b/src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs
@@ -12,7 +12,7 @@ namespace DapperExample.TranslationToSql.DataModel;
///
/// Derives foreign keys and connection strings from an existing Entity Framework Core model.
///
-public sealed class FromEntitiesDataModelService : BaseDataModelService
+public sealed class FromEntitiesDataModelService(IResourceGraph resourceGraph) : BaseDataModelService(resourceGraph)
{
private readonly Dictionary _foreignKeysByRelationship = [];
private readonly Dictionary _columnNullabilityPerAttribute = [];
@@ -21,11 +21,6 @@ public sealed class FromEntitiesDataModelService : BaseDataModelService
public override DatabaseProvider DatabaseProvider => AssertHasDatabaseProvider();
- public FromEntitiesDataModelService(IResourceGraph resourceGraph)
- : base(resourceGraph)
- {
- }
-
public void Initialize(DbContext dbContext)
{
_connectionString = dbContext.Database.GetConnectionString();
@@ -43,12 +38,12 @@ public void Initialize(DbContext dbContext)
Initialize();
}
- private void ScanForeignKeys(IModel entityModel)
+ private void ScanForeignKeys(IReadOnlyModel entityModel)
{
foreach (RelationshipAttribute relationship in ResourceGraph.GetResourceTypes().SelectMany(resourceType => resourceType.Relationships))
{
- IEntityType? leftEntityType = entityModel.FindEntityType(relationship.LeftType.ClrType);
- INavigation? navigation = leftEntityType?.FindNavigation(relationship.Property.Name);
+ IReadOnlyEntityType? leftEntityType = entityModel.FindEntityType(relationship.LeftType.ClrType);
+ IReadOnlyNavigation? navigation = leftEntityType?.FindNavigation(relationship.Property.Name);
if (navigation != null)
{
@@ -62,7 +57,7 @@ private void ScanForeignKeys(IModel entityModel)
}
}
- private void ScanColumnNullability(IModel entityModel)
+ private void ScanColumnNullability(IReadOnlyModel entityModel)
{
foreach (ResourceType resourceType in ResourceGraph.GetResourceTypes())
{
@@ -70,15 +65,15 @@ private void ScanColumnNullability(IModel entityModel)
}
}
- private void ScanColumnNullability(ResourceType resourceType, IModel entityModel)
+ private void ScanColumnNullability(ResourceType resourceType, IReadOnlyModel entityModel)
{
- IEntityType? entityType = entityModel.FindEntityType(resourceType.ClrType);
+ IReadOnlyEntityType? entityType = entityModel.FindEntityType(resourceType.ClrType);
if (entityType != null)
{
foreach (AttrAttribute attribute in resourceType.Attributes)
{
- IProperty? property = entityType.FindProperty(attribute.Property.Name);
+ IReadOnlyProperty? property = entityType.FindProperty(attribute.Property.Name);
if (property != null)
{
diff --git a/src/Examples/DapperExample/TranslationToSql/Generators/ParameterGenerator.cs b/src/Examples/DapperExample/TranslationToSql/Generators/ParameterGenerator.cs
index bd4df111fc..aaa328e68f 100644
--- a/src/Examples/DapperExample/TranslationToSql/Generators/ParameterGenerator.cs
+++ b/src/Examples/DapperExample/TranslationToSql/Generators/ParameterGenerator.cs
@@ -20,11 +20,5 @@ public void Reset()
_nameGenerator.Reset();
}
- private sealed class ParameterNameGenerator : UniqueNameGenerator
- {
- public ParameterNameGenerator()
- : base("@p")
- {
- }
- }
+ private sealed class ParameterNameGenerator() : UniqueNameGenerator("@p");
}
diff --git a/src/Examples/DapperExample/TranslationToSql/Generators/TableAliasGenerator.cs b/src/Examples/DapperExample/TranslationToSql/Generators/TableAliasGenerator.cs
index 39d5d9d702..a63bfdc01a 100644
--- a/src/Examples/DapperExample/TranslationToSql/Generators/TableAliasGenerator.cs
+++ b/src/Examples/DapperExample/TranslationToSql/Generators/TableAliasGenerator.cs
@@ -3,10 +3,4 @@ namespace DapperExample.TranslationToSql.Generators;
///
/// Generates a SQL table alias with a unique name.
///
-internal sealed class TableAliasGenerator : UniqueNameGenerator
-{
- public TableAliasGenerator()
- : base("t")
- {
- }
-}
+internal sealed class TableAliasGenerator() : UniqueNameGenerator("t");
diff --git a/src/Examples/DapperExample/TranslationToSql/Transformations/StaleColumnReferenceRewriter.cs b/src/Examples/DapperExample/TranslationToSql/Transformations/StaleColumnReferenceRewriter.cs
index 03692baf2d..a632c43e51 100644
--- a/src/Examples/DapperExample/TranslationToSql/Transformations/StaleColumnReferenceRewriter.cs
+++ b/src/Examples/DapperExample/TranslationToSql/Transformations/StaleColumnReferenceRewriter.cs
@@ -290,14 +290,9 @@ private IReadOnlyList VisitList(IEnumerable nodes, ColumnVisitMode mode
return nodes.Select(element => TypedVisit(element, mode)).ToList();
}
- private sealed class PopStackOnDispose : IDisposable
+ private sealed class PopStackOnDispose(Stack stack) : IDisposable
{
- private readonly Stack _stack;
-
- public PopStackOnDispose(Stack stack)
- {
- _stack = stack;
- }
+ private readonly Stack _stack = stack;
public void Dispose()
{
diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInSelectNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInSelectNode.cs
index e4b79fe7eb..2be0561011 100644
--- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInSelectNode.cs
+++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInSelectNode.cs
@@ -10,18 +10,13 @@ namespace DapperExample.TranslationToSql.TreeNodes;
/// SELECT t2.Id AS Id0 FROM (SELECT t1.Id FROM Users AS t1) AS t2
/// ]]>.
///
-internal sealed class ColumnInSelectNode : ColumnNode
+internal sealed class ColumnInSelectNode(ColumnSelectorNode selector, string? tableAlias) : ColumnNode(GetColumnName(selector), selector.Column.Type,
+ tableAlias)
{
- public ColumnSelectorNode Selector { get; }
+ public ColumnSelectorNode Selector { get; } = selector;
public bool IsVirtual => Selector.Alias != null || Selector.Column is ColumnInSelectNode { IsVirtual: true };
- public ColumnInSelectNode(ColumnSelectorNode selector, string? tableAlias)
- : base(GetColumnName(selector), selector.Column.Type, tableAlias)
- {
- Selector = selector;
- }
-
private static string GetColumnName(ColumnSelectorNode selector)
{
ArgumentGuard.NotNull(selector);
diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInTableNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInTableNode.cs
index 8e8aab29ce..cd605e72a4 100644
--- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInTableNode.cs
+++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/ColumnInTableNode.cs
@@ -8,13 +8,8 @@ namespace DapperExample.TranslationToSql.TreeNodes;
/// FROM Users AS t1
/// ]]>.
///
-internal sealed class ColumnInTableNode : ColumnNode
+internal sealed class ColumnInTableNode(string name, ColumnType type, string? tableAlias) : ColumnNode(name, type, tableAlias)
{
- public ColumnInTableNode(string name, ColumnType type, string? tableAlias)
- : base(name, type, tableAlias)
- {
- }
-
public override TResult Accept(SqlTreeNodeVisitor visitor, TArgument argument)
{
return visitor.VisitColumnInTable(this, argument);
diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/CountSelectorNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/CountSelectorNode.cs
index 07ad67f144..d0b9e18ca2 100644
--- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/CountSelectorNode.cs
+++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/CountSelectorNode.cs
@@ -8,13 +8,8 @@ namespace DapperExample.TranslationToSql.TreeNodes;
/// SELECT COUNT(*) FROM Users
/// ]]>.
///
-internal sealed class CountSelectorNode : SelectorNode
+internal sealed class CountSelectorNode(string? alias) : SelectorNode(alias)
{
- public CountSelectorNode(string? alias)
- : base(alias)
- {
- }
-
public override TResult Accept(SqlTreeNodeVisitor visitor, TArgument argument)
{
return visitor.VisitCountSelector(this, argument);
diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/FromNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/FromNode.cs
index 8ec4ab5c20..3d29636212 100644
--- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/FromNode.cs
+++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/FromNode.cs
@@ -5,13 +5,8 @@ namespace DapperExample.TranslationToSql.TreeNodes;
/// FROM Customers AS t1
/// ]]>.
///
-internal sealed class FromNode : TableAccessorNode
+internal sealed class FromNode(TableSourceNode source) : TableAccessorNode(source)
{
- public FromNode(TableSourceNode source)
- : base(source)
- {
- }
-
public override TResult Accept(SqlTreeNodeVisitor visitor, TArgument argument)
{
return visitor.VisitFrom(this, argument);
diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/OneSelectorNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/OneSelectorNode.cs
index c86aea6d63..a9c05301a7 100644
--- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/OneSelectorNode.cs
+++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/OneSelectorNode.cs
@@ -8,13 +8,8 @@ namespace DapperExample.TranslationToSql.TreeNodes;
/// SELECT 1 FROM Users
/// ]]>.
///
-internal sealed class OneSelectorNode : SelectorNode
+internal sealed class OneSelectorNode(string? alias) : SelectorNode(alias)
{
- public OneSelectorNode(string? alias)
- : base(alias)
- {
- }
-
public override TResult Accept(SqlTreeNodeVisitor visitor, TArgument argument)
{
return visitor.VisitOneSelector(this, argument);
diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/OrderByTermNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/OrderByTermNode.cs
index 2c3fc80b3e..89fb4c219d 100644
--- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/OrderByTermNode.cs
+++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/OrderByTermNode.cs
@@ -3,12 +3,7 @@ namespace DapperExample.TranslationToSql.TreeNodes;
///
/// Represents the base type for terms in an .
///
-internal abstract class OrderByTermNode : SqlTreeNode
+internal abstract class OrderByTermNode(bool isAscending) : SqlTreeNode
{
- public bool IsAscending { get; }
-
- protected OrderByTermNode(bool isAscending)
- {
- IsAscending = isAscending;
- }
+ public bool IsAscending { get; } = isAscending;
}
diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/SelectorNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/SelectorNode.cs
index 8a47a8af66..44ee14ea17 100644
--- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/SelectorNode.cs
+++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/SelectorNode.cs
@@ -3,12 +3,7 @@ namespace DapperExample.TranslationToSql.TreeNodes;
///
/// Represents the base type for selectors in a .
///
-internal abstract class SelectorNode : SqlTreeNode
+internal abstract class SelectorNode(string? alias) : SqlTreeNode
{
- public string? Alias { get; }
-
- protected SelectorNode(string? alias)
- {
- Alias = alias;
- }
+ public string? Alias { get; } = alias;
}
diff --git a/src/Examples/DapperExample/TranslationToSql/TreeNodes/TableSourceNode.cs b/src/Examples/DapperExample/TranslationToSql/TreeNodes/TableSourceNode.cs
index 6628ed11dc..62ff5ad3ef 100644
--- a/src/Examples/DapperExample/TranslationToSql/TreeNodes/TableSourceNode.cs
+++ b/src/Examples/DapperExample/TranslationToSql/TreeNodes/TableSourceNode.cs
@@ -5,17 +5,12 @@ namespace DapperExample.TranslationToSql.TreeNodes;
///
/// Represents the base type for tabular data sources, such as database tables and sub-queries.
///
-internal abstract class TableSourceNode : SqlTreeNode
+internal abstract class TableSourceNode(string? alias) : SqlTreeNode
{
public const string IdColumnName = nameof(Identifiable