- PVSM.RU - https://www.pvsm.ru -

Расширения LINQ для Azure Table Storage, реализующие Or и Contains

Всем привет! Рад представить вам уже пятую статью из цикла «Внутреннее устройство и архитектура сервиса AtContent.com». В ней я расскажу о том как сделать работу с Azure Table Storage более функциональной и удобной.

LINQ

Платформа Windows Azure дает очень мощный набор инструментов для реализации своих идей. И среди них – Azure Table Storage – нереляционная база данных с неограниченным объемом. Большим плюсом этого хранилища является то, что можно делать к нему достаточно сложные запросы. Но помимо этого есть и некоторые неудобства. Так, например, с помощью LINQ нельзя выполнить запросы, в которых есть логика Or или Contains без дополнительных модификаций.

Когда же нам пришлось с этим столкнуться мы изучили проблему и окзалось, что с помощью REST API можно делать запросы в которых есть Or. И нам очень нужны были такие запросы. Не переписывать же теперь весь код с LINQ на REST API ради этого. Копнув чуть глубже мы нашли решение!

Итак, чтобы сохранить удобный способ работы с Azure Table Storage – LINQ – и дополнить его возможность делать Contains-запросы потребовалось разобрать механизмы, которые происходят в глубинах LINQ при трансформации запроса в REST API. Сама проблема заключается в том, что динамически модифицировать LINQ-запрос у нас получилось только с помощью Where. При этом выражение добавляется к запросу с модификатором And. Поэтому составить запрос с переменным числом параметров, соединенных оператором And не представляет никакой сложности. Если же потребуется сделать то же самое для Or – придется привлекать на помощь лямда-выражения.

Конечно же можно просто сделать несколько запросов и затем объединить их в один, но подобная расточительность негативно сказывается на конечной стоимости решения для ваших покупателей или подписчиков. Даже при стоимости одного запроса в сотые доли цента разница между одним запросом и, скажем, десятью очевидна. Поэтому перейдем к нашему решению.

Первая попытка закончилась неудачей. Поисковые системы ничем не могли помочь в данном вопросе и поэтому приходилось действовать с помощью эксперимента. В IQuerable.Where() можно подставить Expression<Func<T,bool>>. С горем пополам составив такое выражение и опробовав его меня постигло разочарование. Полученное выражение после Invoke() включало в себя имена классов и такой запрос к Azure Table Storage конечно же возвращал исключение.

Но это меня не остановило. Перспектива использовать REST API вместо LINQ не давала мне уснуть. Всего пара дней интенсивного чтения MSDN и родилось элегантное решение, которое можно очень просто применять.

public static class LinqExtension
{
    public static Expression<Func<T, bool>> Contains<T>(string ObjectStringName, string FieldStringNameToCompare, IList<String> ListOfValues)
    {
        Expression ExprBody = null;
        ParameterExpression ParameterObject = Expression.Parameter(typeof(T), ObjectStringName);
        var PropertyFieldToCompare = Expression.Property(ParameterObject, FieldStringNameToCompare);
        foreach (var ValueToCompare in ListOfValues)
        {
            ConstantExpression ValueConst = Expression.Constant(ValueToCompare, typeof(string));
            BinaryExpression EqualTermExpression = Expression.Equal(PropertyFieldToCompare, ValueConst);
            if (ExprBody == null) { ExprBody = EqualTermExpression; }
            else
            {
                ExprBody = Expression.Or(ExprBody, EqualTermExpression);
            }
        }
        if (ExprBody == null)
        {
            ExprBody = Expression.IsTrue(Expression.Constant(false));
        }
        var FinalExpression = Expression.Lambda<Func<T, bool>>(ExprBody, new ParameterExpression[] { ParameterObject });
        return FinalExpression;
    }

    public static IQueryable<T> Contains<T>(this IQueryable<T> obj, string ObjectStringName, string FieldStringNameToCompare, IList<String> ListOfValues)
    {
        var Expression = Contains<T>(ObjectStringName, FieldStringNameToCompare, ListOfValues);
        if (Expression != null)
            return obj.Where<T>(Expression);
        return obj;
    }
}

Оно позволяет применить к запросу операцию Contains. Таким образом получается аналог операции IN в SQL. Это позволило значительно расширить применимость Azure Table Storage в нашем проекте.

В этой реализации слеудет отметить, что лямбда-выражение конструируется поэтапно и та самая строчка, которая не давала мне уснуть пару дней выглядит так:

ConstantExpression ValueConst = Expression.Constant(ValueToCompare, typeof(string));

Именно она указывает, что при выполнении Invoke() нужно подставлять константу, а не значение объекта.
Приведенная здесь реализация может работать только со строковыми значениями, но ничего не мешает вам самостоятельно расширить эти методы для применения с любыми типами.

Описаное в статье решение входит в Open Source библиотеку CPlase, которая готовится к публикации.

Читайте в серии:

Также приглашаю опробовать наш сервис
AtContent™ [5]

Автор: VadNov


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/net/6045

Ссылки в тексте:

[1] AtContent.com. Внутреннее устройство и архитектура: http://habrahabr.ru/post/140418/

[2] Механизм обмена сообщениями между ролями и экземплярами: http://habrahabr.ru/post/140461/

[3] Кэширование данных на экземпляре и управление кешированием: http://habrahabr.ru/post/140955/

[4] Эффективное управление облачными очередями (Azure Queue): http://habrahabr.ru/post/141797/

[5] Image: https://atcontent.com