diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 575923d62c..9658fff117 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
- "version": "2023.3.2",
+ "version": "2023.3.3",
"commands": [
"jb"
]
@@ -15,7 +15,7 @@
]
},
"dotnet-reportgenerator-globaltool": {
- "version": "5.2.0",
+ "version": "5.2.1",
"commands": [
"reportgenerator"
]
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 33633bd3b8..8f1c272506 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -138,7 +138,9 @@ jobs:
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
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/JsonApiDotNetCore.sln.DotSettings b/JsonApiDotNetCore.sln.DotSettings
index 077dd5e21f..9b756680ec 100644
--- a/JsonApiDotNetCore.sln.DotSettings
+++ b/JsonApiDotNetCore.sln.DotSettings
@@ -63,6 +63,7 @@ JsonApiDotNetCore.ArgumentGuard.NotNull($EXPR$);
SUGGESTION
HINT
WARNING
+ SUGGESTION
DO_NOT_SHOW
HINT
SUGGESTION
diff --git a/ROADMAP.md b/ROADMAP.md
index 270ae294e6..7ba6a2a5a8 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -2,12 +2,14 @@
This document provides an overview of the direction this project is heading and lists what we intend to work on in the near future.
-> Disclaimer: This is an open source project. The available time of our contributors varies and therefore we do not plan release dates. This document expresses our current intent, which may change over time.
+> Disclaimer: This is an open-source project. The available time of our contributors varies and therefore we do not plan release dates. This document expresses our current intent, which may change over time.
-We have interest in the following topics. It's too soon yet to decide whether they'll make it into v5.x or in a later major version.
+We have an interest in the following topics. It's too soon yet to decide whether they'll make it into v5.x or in a later major version.
+- OpenAPI (Swagger): Generate documentation and typed clients [#1046](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1046)
+- Query strings on JSON-mapped columns [#1439](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1439)
+- Improved SQL Server support [#1118](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1118)
- Optimistic concurrency [#1119](https://github.com/json-api-dotnet/JsonApiDotNetCore/pull/1119)
-- OpenAPI (Swagger) [#1046](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1046)
- Fluent API [#776](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/776)
- Idempotency [#1132](https://github.com/json-api-dotnet/JsonApiDotNetCore/pull/1132)
@@ -19,3 +21,5 @@ Please give us feedback that will give us insight on the following points:
* Existing features that are missing some capability or otherwise don't work well enough.
* Missing features that should be added to the product.
* Design choices for a feature that is currently in-progress.
+
+Please consider to [sponsor the project](https://github.com/sponsors/json-api-dotnet).
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/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs
index 858532d7c2..d1113946ad 100644
--- a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs
+++ b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs
@@ -93,9 +93,9 @@ private Expression CreateLambdaBodyInitializerForTypeHierarchy(FieldSelection se
private static BinaryExpression CreateRuntimeTypeCheck(LambdaScope lambdaScope, Type concreteClrType)
{
// Emitting "resource.GetType() == typeof(Article)" instead of "resource is Article" so we don't need to check for most-derived
- // types first. This way, we can fallback to "anything else" at the end without worrying about order.
+ // types first. This way, we can fall back to "anything else" at the end without worrying about order.
- Expression concreteTypeConstant = concreteClrType.CreateTupleAccessExpressionForConstant(typeof(Type));
+ Expression concreteTypeConstant = SystemExpressionBuilder.CloseOver(concreteClrType);
MethodCallExpression getTypeCall = Expression.Call(lambdaScope.Accessor, TypeGetTypeMethod);
return Expression.MakeBinary(ExpressionType.Equal, getTypeCall, concreteTypeConstant, false, TypeOpEqualityMethod);
diff --git a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs
index b48fa696db..4c54586cb6 100644
--- a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs
+++ b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs
@@ -36,7 +36,7 @@ public override Expression VisitPagination(PaginationExpression expression, Quer
private static Expression ExtensionMethodCall(Expression source, string operationName, int value, QueryClauseBuilderContext context)
{
- Expression constant = value.CreateTupleAccessExpressionForConstant(typeof(int));
+ Expression constant = SystemExpressionBuilder.CloseOver(value);
return Expression.Call(context.ExtensionType, operationName, [context.LambdaScope.Parameter.Type], source, constant);
}
diff --git a/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs
index f14d795f7b..b5fafdd21b 100644
--- a/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs
+++ b/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs
@@ -245,7 +245,6 @@ public override Expression VisitNullConstant(NullConstantExpression expression,
public override Expression VisitLiteralConstant(LiteralConstantExpression expression, QueryClauseBuilderContext context)
{
- Type type = expression.TypedValue.GetType();
- return expression.TypedValue.CreateTupleAccessExpressionForConstant(type);
+ return SystemExpressionBuilder.CloseOver(expression.TypedValue);
}
}
diff --git a/src/JsonApiDotNetCore/Queries/SystemExpressionBuilder.cs b/src/JsonApiDotNetCore/Queries/SystemExpressionBuilder.cs
new file mode 100644
index 0000000000..f625ee9b4f
--- /dev/null
+++ b/src/JsonApiDotNetCore/Queries/SystemExpressionBuilder.cs
@@ -0,0 +1,34 @@
+using System.Linq.Expressions;
+using System.Reflection;
+
+#pragma warning disable AV1008
+
+namespace JsonApiDotNetCore.Queries;
+
+internal static class SystemExpressionBuilder
+{
+ private static readonly MethodInfo CloseOverOpenMethod =
+ typeof(SystemExpressionBuilder).GetMethods().Single(method => method is { Name: nameof(CloseOver), IsGenericMethod: true });
+
+ // To enable efficient query plan caching, inline constants (that vary per request) should be converted into query parameters.
+ // https://stackoverflow.com/questions/54075758/building-a-parameterized-entityframework-core-expression
+ //
+ // CloseOver can be used to change a query like:
+ // SELECT ... FROM ... WHERE x."Age" = 3
+ // into:
+ // SELECT ... FROM ... WHERE x."Age" = @p0
+
+ public static Expression CloseOver(object value)
+ {
+ ArgumentGuard.NotNull(value);
+
+ MethodInfo closeOverClosedMethod = CloseOverOpenMethod.MakeGenericMethod(value.GetType());
+ return (Expression)closeOverClosedMethod.Invoke(null, [value])!;
+ }
+
+ public static Expression CloseOver(T value)
+ {
+ // From https://github.com/dotnet/efcore/issues/28151#issuecomment-1374480257.
+ return ((Expression>)(() => value)).Body;
+ }
+}
diff --git a/src/JsonApiDotNetCore/Queries/SystemExpressionExtensions.cs b/src/JsonApiDotNetCore/Queries/SystemExpressionExtensions.cs
deleted file mode 100644
index ef81aece33..0000000000
--- a/src/JsonApiDotNetCore/Queries/SystemExpressionExtensions.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System.Linq.Expressions;
-using System.Reflection;
-
-namespace JsonApiDotNetCore.Queries;
-
-internal static class SystemExpressionExtensions
-{
- public static Expression CreateTupleAccessExpressionForConstant(this object? value, Type type)
- {
- // To enable efficient query plan caching, inline constants (that vary per request) should be converted into query parameters.
- // https://stackoverflow.com/questions/54075758/building-a-parameterized-entityframework-core-expression
-
- // This method can be used to change a query like:
- // SELECT ... FROM ... WHERE x."Age" = 3
- // into:
- // SELECT ... FROM ... WHERE x."Age" = @p0
-
- // The code below builds the next expression for a type T that is unknown at compile time:
- // Expression.Property(Expression.Constant(Tuple.Create(value)), "Item1")
- // Which represents the next C# code:
- // Tuple.Create(value).Item1;
-
- MethodInfo tupleCreateUnboundMethod = typeof(Tuple).GetMethods()
- .Single(method => method is { Name: "Create", IsGenericMethod: true } && method.GetGenericArguments().Length == 1);
-
- MethodInfo tupleCreateClosedMethod = tupleCreateUnboundMethod.MakeGenericMethod(type);
-
- ConstantExpression constantExpression = Expression.Constant(value, type);
-
- MethodCallExpression tupleCreateCall = Expression.Call(tupleCreateClosedMethod, constantExpression);
- return Expression.Property(tupleCreateCall, "Item1");
- }
-}
diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs
index 9d282b4938..f8af9dfa2f 100644
--- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs
+++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs
@@ -102,7 +102,7 @@ public NewExpression CreateNewExpression(Type resourceClrType)
{
object constructorArgument = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, constructorParameter.ParameterType);
- Expression argumentExpression = constructorArgument.CreateTupleAccessExpressionForConstant(constructorArgument.GetType());
+ Expression argumentExpression = SystemExpressionBuilder.CloseOver(constructorArgument);
constructorArguments.Add(argumentExpression);
}
#pragma warning disable AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException
diff --git a/test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs b/test/OpenApiClientTests/PropertyInfoAssertionsExtensions.cs
similarity index 100%
rename from test/OpenApiClientTests/PropertyInfoAssertionsExtension.cs
rename to test/OpenApiClientTests/PropertyInfoAssertionsExtensions.cs