StackExchange.Redis переводит RedisValue в byte[] через as byte[] и возвращает null

Я пытаюсь создать поставщика Redis для Strathweb.CacheOutput.WebApi2, но попытка преобразования из byte[] -> RedisValue -> byte[] возвращает значение null.

Я могу вручную установить тип объекта как byte[] вместо var/RedisValue, и он будет правильно возвращать значение как byte[], но после того, как он был установлен как RedisValue, его невозможно преобразовать в byte[].

В его интерфейсе Get всегда возвращает объект, поэтому я не могу форсировать тип или использовать отдельный вызов без необходимости изменять интерфейс.

Если я попытаюсь сделать result as byte[], я получу Cannot convert type 'StackExchange.Redis.RedisValue' to 'byte[]' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion

Если я попытаюсь сделать (byte[])result, я получу Cannot cast 'result' (which has an actual type of 'StackExchange.Redis.RedisValue') to 'byte[]'

Есть ли что-то, что я упускаю, или мне придется как-то взломать это, проверив, какой тип данных он ищет на основе ключа?

Вот интерфейс:

namespace WebApi.OutputCache.Core.Cache
{
    public interface IApiOutputCache
    {
        void RemoveStartsWith(string key);
        T Get<T>(string key) where T : class;
        object Get(string key);
        void Remove(string key);
        bool Contains(string key);
        void Add(string key, object o, DateTimeOffset expiration, string dependsOnKey = null);
        IEnumerable<string> AllKeys { get; }
    }
}

А вот как это называется:

        var val = _webApiCache.Get(cachekey) as byte[];
        if (val == null) return;

Изменить: добавление примеров API, который я реализовал с использованием как ServiceStack.Redis v3 (работает банкомат, так как он просто использует object и StackExchange.Redis, который не работает)

https://github.com/mackayj/WebApi.OutputCache.Redis.ServiceStack

https://github.com/mackayj/WebApi.OutputCache.Redis.StackExchange


person John    schedule 03.10.2014    source источник


Ответы (3)


Преобразование между byte[] и RedisValue использует оператор преобразования. Однако это значение известно компилятору только как object, поэтому он не знает, что ему нужно вызывать оператор преобразования. т.е. если написать следующий код:

object result = someRedisValue;
byte[] bytes = (byte[])result;

Компилятор пишет что-то вроде следующего для последней строки, которая терпит неудачу:

cast result to byte[] // runtime error: it's not a byte[]!
store that in 'bytes'

Вы можете решить эту проблему, сообщив компилятору реальный тип объекта, прежде чем пытаться его преобразовать.

byte[] bytes = (RedisValue)result;

Это заставляет компилятор писать такой код:

cast result to RedisValue
call RedisValue's implicit RedisValue to byte[] conversion on that
store that in 'bytes'
person Tim S.    schedule 03.10.2014
comment
Проблема в том, что интерфейс возвращает объект, а вызывающий код выполняет as byte[]. Я пытаюсь не изменять ни интерфейс, ни основной код для него, но я думаю, что пока просто добавлю новый метод, который возвращает byte[]. Я отредактировал свой пост, чтобы показать правильный код вызова с as byte[] - person John; 03.10.2014

Это интересная проблема. Есть несколько способов приблизиться к этому:

  • преобразовать его в byte[] перед сохранением
  • жульничество с dynamic
  • оберните его в другую идентифицируемую оболочку перед сохранением

По сути, пользовательские операторы преобразования не работают при распаковке, если вы не используете dynamic

Я также мог бы реализовать IConvertible или какой-нибудь другой известный интерфейс.

person Marc Gravell    schedule 03.10.2014
comment
Чем больше я об этом думаю, тем больше убеждаюсь, что RedisValue должен : IConvertible. Вероятно, это будет в следующем дропе. - person Marc Gravell; 03.10.2014
comment
Спасибо за быстрый ответ Марк! Я заменил этот код прямым вызовом, который возвращает byte[] вместо объекта, и заметил, что он фактически терпит неудачу и во многих других местах (вызовы as string и as MediaTypeHeaderValue). Я попытался просто установить его как dynamic result = _database.GetString(key), но это дало тот же результат, что вы имели в виду, говоря об обмане с динамическим? Есть ли способ просто вернуть объект вместо преобразования его в RedisValue? - person John; 03.10.2014
comment
В итоге я просто заменил StackExchange.Redis на ServiceStack.Redis и смог сделать это без проблем. Ваша версия мне больше нравится, так как она намного меньше. Если есть способ просто использовать object, а не RedisValue в качестве типа возвращаемого значения, сообщите мне, чтобы я мог поменять его обратно :) - person John; 03.10.2014
comment
@John, что ты имел в виду под обманом с помощью dynamic - ]вот так; повторно вернуть объект вместо ... RedisValue; нет, потому что весь смысл RedisValue заключается в том, чтобы маскировать строку? капля? счетчик? аспект редиса; конечно, это может быть большой двоичный объект, если вызывающий объект выполняет приведение к большому двоичному объекту ранее; вы также можете просмотреть этот коммит, который обеспечивает IConvertible поддержку для этого. - person Marc Gravell; 03.10.2014
comment
@ Джон, опять же, проблема проста: каким должно быть object? byte[]? string? int? если вы всегда хотите byte[], то просто приведите к byte[] перед object, т. е. RedisValue val = ...; object o = (byte[])val; // <=== use that - person Marc Gravell; 03.10.2014
comment
спасибо за быстрые обновления! Я понимаю, почему у вас есть оболочка, это только для этого интерфейса, который я пытаюсь реализовать для кэширования, который он вызывает как что-то вне кода, который я пишу, и просто ожидает, что это будет возвращаемый объект. Я обновил OP двумя примерами проектов. - person John; 04.10.2014
comment
@John Джон, наверное, я не понимаю, почему ваша реализация IApiOutputCache просто не всегда возвращает byte[] (очевидно, как object). Тогда бы все работало... - person Marc Gravell; 04.10.2014
comment
к сожалению, этот код повторно используется в нескольких местах: один для получения etag (строка), один для данных (byte[]) и один для получения заголовка (MediaTypeHeaderValue), поэтому я не могу просто сказать это всегда преобразовывать в byte[] перед возвратом. Я бы хотел, чтобы это были три отдельных вызова из API, так как тогда это было бы очень легко сделать. - person John; 04.10.2014
comment
@Джон, мне любопытно; что здесь возвращает ServiceStack, что удобнее? - person Marc Gravell; 04.10.2014
comment
Ну, в итоге мне пришлось изменить его, чтобы использовать public T Get<T>(string key) {} вместо того, чтобы просто запрашивать объект, поскольку он отправлял обратно несколько вещей в виде строк (как для ServiceStack.Redis, так и для StackExchange.Redis). Теперь он работает на 100% для ServiceStack.Redis, но поскольку он не может преобразовать RedisValue в T (я не пробовал ваш последний код на github, только пакет nuget), я не смог использовать StackExchange.Redis. Сейчас я попробую код на github, чтобы увидеть, работает ли он :). ServiceStack просто возвращает T со своим _redisClient.Get‹T›(ключом) вместо оболочки RedisValue. - person John; 04.10.2014
comment
он просто возвращает T или object в моем случае, поэтому он передается правильно. Если я попытаюсь использовать StackExchange.Redis, потому что сначала все преобразуется в RedisValue, я получаю ошибки, если пытаюсь выполнить T result = _database.StringGet(key), и если я пытаюсь выполнить приведение после var result = _database.StringGet(key)... return result as T;, я получаю нуль, когда T является byte[]. Я только что попробовал ваш код с github и вернулся с той же проблемой, он дает null при попытке сделать redisValue как byte[]; - person John; 04.10.2014

Следующий код, использующий StackExchange.Redis, может установить/получить значение универсального типа и преобразовать RedisValue в byte[] в процессе, он должен нормально работать для любого сериализуемого типа.

    public static void SetItem<T>(string key, T value)
    {

        IDatabase redDb = GetDB();
        redDb.StringSet(key, ToByteArray<T>(value));
    }

    public static T GetItem<T>(string key)
    {
        IDatabase redDb = GetDB();
        RedisValue redisResult = redDb.StringGet(key);
        T objResult = FromByteArray<T>(redisResult);
        return objResult;
    }

   public static byte[] ToByteArray<T>(T obj)
   {
        if (obj == null)
            return null;
        BinaryFormatter bf = new BinaryFormatter();
        using (MemoryStream ms = new MemoryStream())
        {
            bf.Serialize(ms, obj);
            return ms.ToArray();
        }
    }

    public static T FromByteArray<T>(byte[] data)
    {
        if (data == null)
            return default(T);
        BinaryFormatter bf = new BinaryFormatter();
        using (MemoryStream ms = new MemoryStream(data))
        {
            object obj = bf.Deserialize(ms);
            return (T)obj;
        }
    }
person Basyonic    schedule 15.10.2016