30 июля 2011 г.

Как вычислить произведение всех чисел в столбце таблицы одним SQL запросом

Я думаю, каждый знает такую статистическую функцию SQL, как SUM(). Она позволяет посчитать сумму всех значений по определённому столбцу (если не задано дополнительных условий). Но что, если нам вдруг понадобиться посчитать не сумму, а произведение всех значений?

Как ни странно, функция SUM() нам тоже пригодится. Как нам от произведения перейти к сумме? Вспомним начала матанализа: логарифм произведения равен сумме логарифмов. Будем использовать натуральный логарифм в паре с функцией возведения экспоненты в степень EXP(). Привожу пример для SQL SERVER, но должно работать и в других СУБД:

SELECT EXP(SUM(LOG(FIELD))) FROM TABLE

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

Вот так мы от суммы переходим к произведению в рамках одного изящного SQL запроса. С ходу не могу сказать, может ли это пригодиться в реальных проектах, но согласитесь - выглядит впечатляюще.

То же самое, только уже средствами LINQ to Objects:

// произведение этих чисел равно 120
int[] array = new int[] { 1, 2, 3, 4, 5 };

var result = Math.Exp((from a in array
  select Math.Log(a)).Sum());

Console.WriteLine("Произведение чисел равно: {0}", result);
Console.ReadKey();

В результате программа выдаст 120.

2 июля 2011 г.

machineKey и Web-фабрика

В конфигурационном файле web.config есть раздел machineKey. В этом разделе можно устанавливать специфический для каждого сервера ключ, который будет использоваться для шифрования данных (например, во ViewState) и создания цифровых подписей. Обычно там указывается, что ключи должны генерироваться автоматически.

Но что делать, если на вашем хостинге используется Web-фабрика, т.е. одно и то же приложение запускается на множестве компьютеров? Если вы запросите страницу - она вначале будет обработана одним сервером, после чего вы отсылаете страницу, которая будет обрабатываться уже другим сервером, то второй сервер не сможет расшифровать состояние просмотра и cookie-файлы форм из первого сервера.

Чтобы решить эту проблему, вам нужно явным образом определить ключи validationKey и decryptionKey в machineKey:
<machineKey validationKey="6AF567A161DC96137E92FFD7FA3E5D1F7915C8CAD80F64E5460B8D87FB43E696F4F494F26A804CA04EF7CF2DF6EF9787FA7B9F9511FFE59DBF10941E7A53A1D1" decryptionKey="2DFB44AB71DDB59D24E714DC20759BFDA027ED89B2E7502C" validation="SHA1" />
Но если вы напишете эти ключи вручную, это будет не совсем случайно. Поэтому предлагаю использовать вот такой метод:
public static string CreateMachineKey(int length)
{
byte[] random = new byte[length / 2];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(random);
StringBuilder machineKey = new StringBuilder(length);
for (int i = 0; i < random.Length; i++)
{
machineKey.Append(string.Format("{0:X2}", random[i]));
}
return machineKey.ToString();
}
Здесь используется криптографически строгий генератор случайных чисел. Каждое случайное число отображается в шестнадцатеричном формате.

Далее, для генерации готового раздела machineKey можно воспользоваться вот такой конструкцией:
string.Format("<machineKey validationKey=\"{0}\" decryptionKey=\"{1}\" validation=\"SHA1\" />", CreateMachineKey(128), CreateMachineKey(48));
Полученную строку нужно будет скопировать в конфигурационный файл.

Для обеспечения максимальной криптозащиты, validationKey должен быть длиной 128 символов, а decryptionKey - 48.

1 июля 2011 г.

Свойство DefaultButton для контрола Panel

На страницах ASP.NET бывает удобно группировать элементы управления, которые связаны между собой по смыслу, внутри одного контрола Panel, который в браузер пользователя приходит в конечном счёте как обыкновенный <div>. Особенно если у этой панели заданы видимые границы.

Рассмотрим такой пример. Пусть на странице имеется две панели, в каждой есть поле ввода и кнопка. Логично предположить, что если ввести что-нибудь в текстовое поле в любой из этих панелей и нажать Enter, то выполнится действие, аналогичное действию кнопки в этой же панели. Но если специально это не указать, то при нажатии Enter'а в любом текстовом поле на странице будет происходить одно и то же действие, как будто всякий раз происходит нажатие на одну и ту же кнопку, что может запутать пользователя.

Поэтому воспользуемся свойством DefaultButton контрола Panel:

<asp:Panel ID="NavigateBySaleNumberPanel" runat="server" DefaultButton="NavigateBySaleNumberButton">
<span>№ объявления для быстрого перехода:</span>
<asp:TextBox ID="SaleNumberTextBox" runat="server" Width="55px"></asp:TextBox>
<asp:Button ID="NavigateBySaleNumberButton" runat="server" Text="Перейти" 
onclick="NavigateBySaleNumberButton_Click" />
</asp:Panel>
<asp:Panel runat="server" ID="SearchPanel" DefaultButton="SearchButton">
<span>Введите текст для поиска:</span>
<asp:TextBox ID="SearchTextBox" runat="server"></asp:TextBox>
<asp:Button ID="SearchButton" runat="server" Text="Найти объявления" OnClick="SearchButton_Click" />
</asp:Panel>

Теперь у каждой панели есть своя "кнопка по умолчанию".