Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to use Azure Cache for Redis #18

Closed
hallidev opened this issue Apr 3, 2020 · 14 comments
Closed

Unable to use Azure Cache for Redis #18

hallidev opened this issue Apr 3, 2020 · 14 comments

Comments

@hallidev
Copy link

hallidev commented Apr 3, 2020

Summary of the issue

I just switched from EFCoreSecondLevelCache (the old package) to this new package, and I'm unable to get Redis caching to work using Azure Cache for Redis. I have a Standard Tier Redis cache configured in Azure like so:

image

The Redis cache config looks like this:

                cacheConfigurationBuilder
                    .WithJsonSerializer(cacheJsonSerializerSettings, cacheJsonSerializerSettings)
                    .WithRedisConfiguration(RedisCacheConfigKey, config =>
                    {
                        config.WithAllowAdmin()
                            .WithPassword(redisConfig.Password)
                            .WithDatabase(0)
                            .WithSsl(redisConfig.Endpoint)
                            .WithEndpoint(redisConfig.Endpoint, redisConfig.Port)
                        .EnableKeyspaceEvents();
                    })
                    .WithRedisCacheHandle(RedisCacheConfigKey)
                    .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10));

With all of the above, any query run against the DbContext throws this exception:

'ERR unknown command CONFIG, with args beginning with: GET, notify-keyspace-events, '

Stack Trace:

at StackExchange.Redis.ConnectionMultiplexer.ExecuteSyncImpl[T](Message message, ResultProcessor1 processor, ServerEndPoint server) in C:\projects\stackexchange-redis\src\StackExchange.Redis\ConnectionMultiplexer.cs:line 2231 at StackExchange.Redis.RedisBase.ExecuteSync[T](Message message, ResultProcessor1 processor, ServerEndPoint server) in C:\projects\stackexchange-redis\src\StackExchange.Redis\RedisBase.cs:line 54
at StackExchange.Redis.RedisServer.ExecuteSync[T](Message message, ResultProcessor1 processor, ServerEndPoint server) in C:\projects\stackexchange-redis\src\StackExchange.Redis\RedisServer.cs:line 585 at StackExchange.Redis.RedisServer.ConfigGet(RedisValue pattern, CommandFlags flags) in C:\projects\stackexchange-redis\src\StackExchange.Redis\RedisServer.cs:line 152 at CacheManager.Redis.RedisConnectionManager.GetConfiguration(String key) at CacheManager.Redis.RedisCacheHandle1..ctor(ICacheManagerConfiguration managerConfiguration, CacheHandleConfiguration configuration, ILoggerFactory loggerFactory, ICacheSerializer serializer)

@VahidN
Copy link
Owner

VahidN commented Apr 3, 2020

This error is related to EnableKeyspaceEvents() setting.

It enables keyspace notifications to react on eviction/expiration of items.
Make sure that all servers are configured correctly and 'notify-keyspace-events' is at least set to 'Exe', otherwise CacheManager will not retrieve any events.
See https://redis.io/topics/notifications#configuration for configuration details.

If you can't set it, just remove that line.

@hallidev
Copy link
Author

hallidev commented Apr 3, 2020

@VahidN Appreciate the quick response. Great library by the way, I've been relying on it for a while now.

When I remove EnableKeyspaceEvents(), all my queries throw the following exception:

NullReferenceException:

at EFCoreSecondLevelCacheInterceptor.EFTableRowsDataReader.GetInt16(Int32 ordinal)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.AsyncEnumerator.<MoveNextAsync>d__17.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.<ToListAsync>d__641.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.d__641.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult()

@VahidN
Copy link
Owner

VahidN commented Apr 3, 2020

  • You can try Egx or eA value for the notify-keyspace-events too.
  • What's your setting for GetInt16 field? What db is in use?

@hallidev
Copy link
Author

hallidev commented Apr 3, 2020

On closer inspection, only .Cacheable() queries are throwing the NullReferenceException. I've tried switching between the keyspace event types - same result.

It's not just GetInt16 that's failing - it seems it fails on any GetXXX call with the NullReferenceException.

What do you mean by settings for GetInt16 though?

Also, I'm using an Azure SQL database.

@VahidN
Copy link
Owner

VahidN commented Apr 3, 2020

Probably you have a nullable property. It's fixed via #b172fed

@hallidev
Copy link
Author

hallidev commented Apr 3, 2020

Ah yes - I have many nullable properties. I'm sure that's it.

Funny enough, I installed 1.4.0 this morning and noticed you released 1.5.0 shortly afterwards. Will you be pushing another nuget package?

Again, your work on this is very much appreciated.

@hallidev
Copy link
Author

hallidev commented Apr 3, 2020

I just took a quick at your change - would it only affect Redis caching? The in-memory caching seems to be working fine, but I'm not sure if that uses a totally different mechanism.

@VahidN
Copy link
Owner

VahidN commented Apr 3, 2020

  • NuGet's site waits for 5 minutes to make the uploaded package public. It's public now.
  • Yes, the Redis one uses a different serializer behind the scene.

@hallidev
Copy link
Author

hallidev commented Apr 3, 2020

Just took the package and it does get quite a bit further, but ultimately fails on objects that have a nullable DateTime:

System.InvalidCastException: 'Unable to cast object of type 'System.DateTime' to type 'System.DateTimeOffset'.'

   at System.Data.Common.DbDataReader.GetFieldValue[T](Int32 ordinal)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.AsyncEnumerator.<MoveNextAsync>d__17.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.<SingleOrDefaultAsync>d__22`1.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.<SingleOrDefaultAsync>d__22`1.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()

None of my values are stored as DateTimeOffset - they're all defined like this:

public DateTime? LastLoginUtc { get; set; }

@VahidN
Copy link
Owner

VahidN commented Apr 3, 2020

Try setting:

var jss = new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore,
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                TypeNameHandling = TypeNameHandling.Objects, // set this if you have binary data

                DateFormatHandling = DateFormatHandling.IsoDateFormat,
                DateParseHandling = DateParseHandling.DateTimeOffset,
                DateTimeZoneHandling = DateTimeZoneHandling.Utc,
            };

@VahidN
Copy link
Owner

VahidN commented Apr 4, 2020

This is a better solution and converts special data types correctly:

var jss = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    TypeNameHandling = TypeNameHandling.Objects, // set this if you have binary data
    Converters = { new SpecialTypesConverter() }
};

And here is the definition of the SpecialTypesConverter.

@VahidN VahidN closed this as completed Apr 4, 2020
@hallidev
Copy link
Author

hallidev commented Apr 4, 2020

So strange - I'm still getting the same exception.

I should have mentioned this above, but I've been using the following JsonSerializerSettings with EFCoreSecondLevelCache for a long time:

            var cacheJsonSerializerSettings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                NullValueHandling = NullValueHandling.Ignore,
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };

When I'm developing locally I use CacheManager's memory cache, and in other environments use Redis.

My memory cache config looks like this, and is currently working fine with the new package:

            cacheConfigurationBuilder
                .WithJsonSerializer(cacheJsonSerializerSettings, cacheJsonSerializerSettings)
                .WithMicrosoftMemoryCacheHandle()
                .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10));

I'd be surprised if this was serialization related as these serialization settings have been working so reliably for so long, but I'll admit I have no clue what the internals of your package look like.

@VahidN
Copy link
Owner

VahidN commented Apr 4, 2020

The mentioned solution has been tested with this entity will all of the possible data types. This is the final settings for the Redis provider.

@hallidev
Copy link
Author

hallidev commented Apr 5, 2020

Just in case this helps anyone in the future -

My issue with the DateTime conversion all boiled down to the property LockoutEnd stored on the base IdentityUser class, which I don't use directly. This property is a DateTimeOffset rather than DateTime.

The proper date type information was being lost as @VahidN points out, and his fix looks to be the correct fix. The issues I was experiencing after implementing the SpecialTypesConverter were related to other spots in code where I use the Redis cache unrelated to EFCoreSecondLevelCacheInterceptor.

My problem was here:

TypeNameHandling = TypeNameHandling.Objects,

My unrelated caching code started deserializing to the JObject type after introducing TypeNameHandling.Objects.

Switching back to TypeNameHandling.Auto along with the use of SpecialTypesConverter fixed my issue.

My serializer settings now look like this:

            var cacheJsonSerializerSettings = new JsonSerializerSettings
            {
                Converters = { new StrongDateTypesConverter() }, 
                TypeNameHandling = TypeNameHandling.Auto,
                NullValueHandling = NullValueHandling.Ignore,
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };

I guess the takeaway is to be sure to be careful with your serializer settings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants