Всем привет! Рад представить вам уже пятую статью из цикла «Внутреннее устройство и архитектура сервиса AtContent.com». В ней я расскажу о том как сделать работу с Azure Table Storage более функциональной и удобной.
Платформа 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.com. Внутреннее устройство и архитектура»,
- «Механизм обмена сообщениями между ролями и экземплярами»,
- «Кэширование данных на экземпляре и управление кешированием»,
- «Эффективное управление облачными очередями (Azure Queue)»,
- «Практические советы по разделению данных на части, генерация PartitionKey и RowKey для Azure Table Storage».
Также приглашаю опробовать наш сервис
Автор: VadNov