GetStateAsync/GetStateEntryAsync works when using redis but fails with SQL Server
See original GitHub issueI have a small DAPR-enabled service which I originally wrote against the default redis state store. With that, the code worked fine. When I switched the state store to SQL Server, reading from the store started throwing exceptions.
Code:
[HttpPost]
[Route("register")]
public async Task<IActionResult> Register([FromBody] RegistrationRequest model,
[FromServices] DaprClient dapr,
CancellationToken cancellationToken)
{
var profile = new UserProfile { Name = model.Username, PhoneNumber = model.Phone };
await dapr.SaveStateAsync(Storage.SqlName, "test", profile, cancellationToken: cancellationToken)
.ConfigureAwait(false);
return Ok();
}
[HttpPost]
[Route("login")]
public async Task<LoginResponse> Login([FromBody] LoginRequest model,
[FromServices] DaprClient dapr,
CancellationToken cancellationToken)
{
var profile = await dapr.GetStateEntryAsync<UserProfile>(Storage.SqlName,
"test",
cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (profile.Value is null)
{
return new LoginResponse { IsSuccess = false };
}
return new LoginResponse
{
IsSuccess = true,
Roles = new[] { "role_1", "role_x" },
Username = model.Username
};
}
Expected Behavior
Calling the Login
method should succeed.
Actual Behavior
Calling the Register
method succeeds and persists the data to the SQL database:
Subsequently calling the Login
method throws an exception in GetStateEntryAsync
:
System.Text.Json.JsonException: The JSON value could not be converted to DotNetBa.Dapr.Common.Models.UserProfile. Path: $ | LineNumber: 0 | BytePositionInLine: 58.
at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType)
at System.Text.Json.JsonPropertyInfoNotNullable`4.OnRead(ReadStack& state, Utf8JsonReader& reader)
at System.Text.Json.JsonPropertyInfo.Read(JsonTokenType tokenType, ReadStack& state, Utf8JsonReader& reader)
at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
at System.Text.Json.JsonSerializer.ReadCore(Type returnType, JsonSerializerOptions options, Utf8JsonReader& reader)
at System.Text.Json.JsonSerializer.Deserialize(String json, Type returnType, JsonSerializerOptions options)
at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
at Dapr.Client.DaprClientGrpc.GetStateAndETagAsync[TValue](String storeName, String key, Nullable`1 consistencyMode, CancellationToken cancellationToken)
at Dapr.Client.DaprClient.GetStateEntryAsync[TValue](String storeName, String key, Nullable`1 consistencyMode, CancellationToken cancellationToken)
at DotNetBa.Dapr.UserService.Controllers.LoginController.Login(LoginRequest model, DaprClient dapr, CancellationToken cancellationToken) in C:\Source\Personal\dotnetba-dapr\src\DotNetBa.Dapr.UserService\Controllers\LoginController.cs:line 27
at lambda_method(Closure , Object )
at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
If I swap the target store for redis, the same code works as expected.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:5 (2 by maintainers)
Top Results From Across the Web
It was not possible to connect to the redis server(s)
The error you are getting is usually a sign that you have not set abortConnect=false in your connection string. The default value for ......
Read more >What if Redis Stops Working - How Do I Keep My App ...
An interesting question came up on the CacheManager repository in issue 146. What do I do, if the Redis server dies and might...
Read more >RedisConnectionException: No connection is available to ...
Connecting to it fails immediately despite the timeouts I've set. When I tried using an invalid host name and/or invalid port the timeouts...
Read more >Could not connect to redis connection refused - Fix it easily
The most common reason for the connection refused error is that the Redis-Server is not started. Redis server should be started to use...
Read more >Sitefinity fails to initialize if Redis connection string is invalid
If an incorrect Redis connection string is put, the project stays on the spinning cogs and 'initialising'. It never errors and changes, ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Well, I cloned the repo and debugged into the
DaprClientGrpc
to see what’s happening there and it all looks good. TheConvertToAnyAsync
works as I would expect and for my test data, produces aByteString
of length 40:However, when I get the value back from SQL Server, I get back a length of 58:
The actual value I get back is a JSON string containing the original 40 byte payload encoded into base64:
So that seems to indicate that the DAPR SQL component is doing something weird when called by the SDK (and ONLY when called by the SDK - calling the component directly works normally when executed via HTTP, as shown above). Then I realized the SDK is invoking the DAPR API via gRPC so that test above is not representative.
That would seem to indicate that the DAPR SQL component handles HTTP invocation differently than gRPC invocation. So I went and looked into the component source: https://github.com/dapr/components-contrib/blob/b647397b2c81356b62170e4161aa2393fc7ccf03/state/sqlserver/sqlserver.go#L479
That looks suspicious to me (but I’m no Go coder). But that line looks like the component is converting
Value
into JSON. From the docs:And I think this is how the payload is getting turned into a JSON string in the database. It would also explain why it works correctly when I invoke the component via HTTP - the Value there gets serialized into JSON properly in that case.
At this point I think this whole ticket belongs in the dapr/components-contrib repo 😕
This was moved to dapr/components-contrib.