-
-
Notifications
You must be signed in to change notification settings - Fork 102
Open
Description
Using UseStackExchangeRedisCacheProvider, which uses MessagePack by default, I had issues with null values throwing exceptions. I resolved this by providing my own implementation of IEFDataSerializer, which replaces EFMessagePackDBNullFormatter with a formatter which calls reader.ReadNil(), and this has fixed the bug for me.
I don't have a good enough understanding of MessagePack to confidently issue a PR for this but my DbNull formatter looks like this:
/// <summary>
/// Replaces <c>EFMessagePackDBNullFormatter</c> which has a bug: its <c>Deserialize</c>
/// returns <see cref="DBNull.Value" /> without consuming the nil token from the reader,
/// leaving the reader misaligned and causing subsequent reads to fail.
/// </summary>
public sealed class DbNullFormatter : IMessagePackFormatter<DBNull?>
{
public static readonly DbNullFormatter Instance = new();
private DbNullFormatter()
{
}
public void Serialize(ref MessagePackWriter writer, DBNull? value, MessagePackSerializerOptions options)
=> writer.WriteNil();
public DBNull Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
reader.ReadNil();
return DBNull.Value;
}
}And my IEFDataSerializer implementation looks like this
/// <summary>
/// Replaces <c>EFMessagePackSerializer</c> with an similar implementation that fixes DBNull handling.
/// </summary>
public sealed class CacheSerializer : IEFDataSerializer
{
private static readonly IFormatterResolver _customResolvers = CompositeResolver.Create(
[
// The EFMessagePackDBNullFormatter has a bug where it returns DBNull.Value without consuming the nil token from the reader.
DbNullFormatter.Instance,
NativeDateTimeArrayFormatter.Instance,
NativeDateTimeFormatter.Instance,
NativeDecimalFormatter.Instance,
NativeGuidFormatter.Instance,
TypelessFormatter.Instance,
],
[
NativeDateTimeResolver.Instance,
NativeDecimalResolver.Instance,
NativeGuidResolver.Instance,
GeometryResolver.Instance,
ContractlessStandardResolver.Instance,
StandardResolverAllowPrivate.Instance,
TypelessContractlessStandardResolver.Instance,
DynamicGenericResolver.Instance,
]);
private readonly bool _enableCompression;
/// <summary>
/// High-Level API of MessagePack for C#.
/// </summary>
public CacheSerializer(IOptions<EFCoreSecondLevelCacheSettings> cacheSettings)
{
var options =
cacheSettings.Value.AdditionalData as EFRedisCacheConfigurationOptions ??
throw new InvalidOperationException(message: "Please call the UseStackExchangeRedisCacheProvider() method.");
_enableCompression = options.EnableCompression;
}
private MessagePackSerializerOptions EfMessagePackSerializerOptions
=> field ??= GetSerializerOptions();
/// <summary>
/// Serializes a given value with the specified buffer writer.
/// </summary>
/// <param name="obj"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public byte[] Serialize<T>(T? obj) => MessagePackSerializer.Serialize(obj, EfMessagePackSerializerOptions);
/// <summary>
/// Deserializes a value of a given type from a sequence of bytes.
/// </summary>
/// <param name="data"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T? Deserialize<T>(byte[]? data)
=> data is null ? default : MessagePackSerializer.Deserialize<T>(data, EfMessagePackSerializerOptions);
private MessagePackSerializerOptions GetSerializerOptions()
=> _enableCompression ?
MessagePackSerializerOptions
.Standard
.WithCompression(MessagePackCompression.Lz4BlockArray)
.WithResolver(_customResolvers):
MessagePackSerializerOptions
.Standard
.WithResolver(_customResolvers);
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels