Время прочтения: 7 мин.
Нередко встречаются ситуации, когда в базе данных необходимо хранить различные файлы, имеющие нормальное текстовое отображение, а также информацию об этих файлах. Примерами таких файлов могут быть обычные текстовые документы, SQL-запросы, файлы с исходным кодом программ и многое другое. База данных MS SQL позволяет хранить такие файлы (как и любые другие), используя тип данных VARBINARY, который представляет собой бинарные данные переменной длины. Файл, хранящийся в базе данных в формате VARBINARY имеет следующий вид:
Для того, чтобы иметь возможность прочитать текстовый файл из БД, необходимо перевести его из бинарного вида. Сделать это можно с помощью следующей SQL-команды:
cast([varbinary_field] as varchar(max))
Для того, чтобы облегчить добавление файлов в базу данных, просмотр содержимого этих файлов и навигацию по ним, можно разработать простое приложение на ASP.NET.
Начнем с создания тестовой таблицы в базе данных. Одно из полей должно быть типа VARBINARY(MAX), а в остальных полях можно хранить любую информацию о файле (например, описание, дату добавлению в базу данных, автора и т.п.). В нашем примере создадим таблицу с названием файла, описанием, датой добавления, автором и размером файла:
CREATE TABLE [dbo].[Table] (
[Id] INT identity(1,1) NOT NULL,
[name] NVARCHAR (MAX) NULL,
[description] NVARCHAR (MAX) NULL,
[date] DATE NULL,
[executor] NVARCHAR (MAX) NULL,
[script] VARBINARY (MAX) NULL,
[size] NVARCHAR(MAX) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
Следующим шагом будет написание front-end части нашего веб-приложения. Она будет состоять из нескольких TextBox, предназначенных для ввода информации о добавляемых файлах, элемента FileUpload для выбора файла в диалоговом окне, таблицы GridView, в которой будут отображаться все файлы и многострочного TextBox для просмотра содержимого файла.
Сначала добавим текстбоксы и кнопку “Добавить”. Для их удобного расположения, поместим их в элемент Table:
<asp:Table runat="server" ID="AddScriptTable" CssClass="textboxestable">
<asp:TableRow BorderStyle="None" BorderColor="Red">
<asp:TableCell>
<asp:Label CssClass="label" ID="Label8" runat="server" Text="Описание:"></asp:Label>
<div>
<asp:TextBox CssClass="textboxes" ID="DescriptionTB" runat="server" ></asp:TextBox>
</div>
</asp:TableCell>
<asp:TableCell>
<asp:Label CssClass="label" ID="Label9" runat="server" Text="Дата"></asp:Label>
<div>
<asp:TextBox CssClass="textboxes" ID="DateTB" runat="server"></asp:TextBox>
</div>
</asp:TableCell>
<asp:TableCell>
<asp:Label CssClass="label" ID="Label10" runat="server" Text="Автор"></asp:Label>
<div>
<asp:TextBox CssClass="textboxes" ID="ExecutorTB" runat="server"></asp:TextBox>
</div>
</asp:TableCell>
<asp:TableCell>
<asp:Label CssClass="label" ID="Label11" runat="server" Text="Файл (<10MB)"></asp:Label>
<div>
<asp:FileUpload CssClass="fileupload" ID="fileupload1" runat="server"/>
</div>
</asp:TableCell>
<asp:TableCell>
<div>
<asp:Button ID="AddButton" runat="server" OnClick="AddButton_Click" Text="Добавить" CssClass="button" />
</div>
</asp:TableCell>
</asp:TableRow>
</asp:Table>
Следующим шагом будет добавление текстбокса для просмотра содержимого. Помимо него нужно добавить кнопку, которая будет очищать этот текстбокс.
<div style="text-align: center">
<asp:TextBox CssClass="textboxes" ID="SearchTB" runat="server" ></asp:TextBox>
<asp:Button ID="SearchButton" runat="server" OnClick="SearchButton_Click" Text="Поиск" CssClass="button"/>
<asp:Button ID="ResetButton" runat="server" OnClick="ResetButton_Click" Text="Сбросить" CssClass="button"/>
</div>
Опционально можно предусмотреть возможность поиска по файлам, поэтому добавим элементы, необходимые для реализации поиска. Нам нужен текстбокс для ввода поискового запроса, кнопка поиска и кнопка отмены:
Последним этапом создания front-end части будет добавление таблицы для отображения списка файлов. Она будет построена на основе элемента GridView и будет иметь поле описание с прокруткой, чтобы описание не занимало много места на странице, если оно достаточно обширное. Для этого вместо обычных столбцов BoundField мы будем использовать настраиваемый столбец TemplateField с вложенным в него элементом Label, зададим CSS-стиль (overflow-y: auto) и ограничим максимальную высоту ячейки (max-height: 170px):
<asp:GridView ID="ScriptsGridView" CssClass="table" runat="server" AutoGenerateColumns="false" HorizontalAlign="Center" OnRowCommand="ScriptsGridView_RowCommand">
<Columns>
<asp:BoundField ItemStyle-CssClass="tablecolumn" DataField="id" HeaderText="ID" />
<asp:ButtonField ItemStyle-CssClass="tablecolumn" CommandName="OpenScript" ButtonType="Button" Text="Открыть" ControlStyle-CssClass="button"/>
<asp:BoundField ItemStyle-CssClass="tablecolumn" DataField="name" HeaderText="Название" />
<asp:TemplateField ItemStyle-CssClass="tablecolumn" HeaderText="Описание">
<ItemTemplate>
<div style="overflow-y: auto; width: 430px; max-height: 170px">
<asp:Label CssClass="label" ID="Label100" runat="server" Text='<%# Bind("description") %>'></asp:Label>
</div>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField ItemStyle-CssClass="tablecolumn" DataField="date" HeaderText="Дата" />
<asp:BoundField ItemStyle-CssClass="tablecolumn" DataField="executor" HeaderText="Автор" />
<asp:BoundField ItemStyle-CssClass="tablecolumn" DataField="size" HeaderText="Размер" />
</Columns>
<RowStyle Font-Size="11" />
<AlternatingRowStyle BackColor="#c6f7ff" />
<HeaderStyle CssClass="tablecolumn" BackColor="#8bc8fe" ForeColor="White" />
</asp:GridView>
Теперь перейдем к back-end части приложения. Для сокращения объема кода в статье, будут выбраны только важные его части. В первую очередь при загрузке страницы необходимо заполнить GridView данными из БД. Для этого воспользуемся стандартным простым способом. SQL Запрос, формирующий выборку для объекта SqlDataAdapter самый простой: “select * from dbo.[table] order by id”. Он подается на вход данного метода:
public void BindData(string command)
{
conn.Close();
ds = new DataSet();
cmd = new SqlCommand(command, conn);
da = new SqlDataAdapter(cmd);
da.Fill(ds);
try
{
conn.Open();
cmd.ExecuteNonQuery();
ScriptsGridView.DataSource = ds;
ScriptsGridView.DataBind();
conn.Close();
}
finally
{
conn.Close();
}
}
Для добавления записи в базу данных необходимо использовать следующий код, где query — обычный INSERT-запрос с подстановкой переменных:
warningLabel.Text = "";
string check = checkInput();
if (check == "")
{
string query = "insert into [table] ([name], [description], [date], [executor], [script], [size]) values(@name,@description,@Date,@Executor,@Script, @size)";
string date = DateTB.Text.Substring(6) + DateTB.Text.Substring(3, 2) + DateTB.Text.Substring(0, 2);
string size = GetFileSize(fileupload1.PostedFile);
SqlCommand cmd = new SqlCommand(query);
cmd.Connection = conn;
cmd.Parameters.AddWithValue("@name", fileupload1.FileName);
cmd.Parameters.AddWithValue("@description", DescriptionTB.Text);
cmd.Parameters.AddWithValue("@Date", date);
cmd.Parameters.AddWithValue("@Executor", ExecutorTB.Text);
cmd.Parameters.AddWithValue("@Script", UploadFileToDB(fileupload1.PostedFile));
cmd.Parameters.AddWithValue("@size", size);
cmd.Connection = conn;
try
{
cmd.Connection.Open();
cmd.ExecuteNonQuery();
cmd.Connection.Close();
BindData("select * from [table] order by [id] desc");
Reset();
}
finally
{
conn.Close();
}
}
else
{
warningLabel.Text = check;
}
Метод UploadFileToDB(), отвечающий за загрузку файла с базу данных, берет файл из элемента FileUpload, переводит его из входного потока в массив байтов с помощью BinaryReader и возвращает этот массив. Метод выглядит следующим образом:
using (Stream fs = fileupload1.PostedFile.InputStream)
{
using (BinaryReader br = new BinaryReader(fs))
{
byte[] bytes = br.ReadBytes((int)fs.Length);
return bytes;
}
}
Для того, чтобы вывести на интерфейс приложения текст файла, хранящегося в базе данных, необходимо перевести его обратно в читаемый вид. Для этого воспользуемся SQL-запросом, который получит бинарное представление файла из таблицы, а затем переведем в текст и отобразим. Открытие файла будет происходить при нажатии кнопки, расположенной в таблице рядом с файлом. Текст будет выводиться в ранее созданное окно.
int index = Convert.ToInt32(e.CommandArgument);
TableCellCollection lineCells = ScriptsGridView.Rows[index].Cells;
string id = lineCells[0].Text;
SqlCommand cmd = new SqlCommand(string.Format("select [script] from [table] where [script] is not null and [id] = {0} order by id desc", id), conn);
SqlDataAdapter adap = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
adap.Fill(ds);
DataRow row = ds.Tables[0].Rows[0];
byte[] data = (byte[])row[0];
scriptTB.Text = Encoding.UTF8.GetString(data);
Поиск среди добавленных файлов будет работать на основе простого SQL-запроса (select * from [table] where [description] like ‘%{0}%’ or [name] like ‘%{0}%’ …) и будет искать информацию по всем свойствам файла, которые мы указали при добавлении. Метод поиска выглядит следующим образом:
protected void SearchButton_Click(object sender, EventArgs e)
{
string searchTemplate = SearchTB.Text.Trim();
string searchQuery = string.Format("select * from [table] where [description] like '%{0}%' or [name] like '%{0}%' or executor like '%{0}%' order by [id] desc", searchTemplate);
BindData(searchQuery);
}
Помимо всего этого, можно реализовать не столь нужные, но удобные вещи, которые еще больше облегчат работу или исключат некоторые возможные ошибки в работе веб-приложения. Среди них:
- Проверка заполненности полей.
- Подсвечивание незаполненных полей.
- Проверка формата даты.
- Ограничение размера загружаемого файла.
- Очистка всех полей кнопкой.
- Автоматическое получение размера файла удобном формате.
Код данных фишек представлен ниже в порядке их упоминания (Методы 1-4 вызываются каждый раз при нажатии кнопки “Добавить” и связаны между собой вызовами):
protected void HighlightEmptyInputs(TextBox[] textBoxes)
{
foreach (TextBox t in textBoxes)
{
if (t.Text.Equals(string.Empty))
{
t.BorderColor = Color.Red;
t.BorderWidth = 2;
}
}
if (!fileupload1.HasFile)
fileupload1.BorderColor = Color.Red;
}
protected bool CheckDate(string date)
{
if (date.Length != 10 || date.ToCharArray()[2] != '.' || date.ToCharArray()[5] != '.')
return false;
else if (!DateTime.TryParse(date, out DateTime dt) )
return false;
else
return true;
}
if (fileupload1.PostedFile.ContentLength > 10485760)
return "Файл превышает 10 MB.";
private void Reset()
{
DescriptionTB.Text = string.Empty;
DateTB.Text = string.Empty;
ExecutorTB.Text = string.Empty;
scriptTB.Text = string.Empty;
}
protected string GetFileSize(HttpPostedFile file)
{
int size = file.ContentLength;
string s_size = string.Concat(size.ToString(), " B");
if (size >= 1024)
{
s_size = string.Concat((size / 1024).ToString(), '.', (size % 1024).ToString().Substring(0, 2), " KB");
if(size>=Math.Pow(1024, 2))
{
int MB = (int)Math.Pow(1024, 2);
s_size = string.Concat((size / MB).ToString(), '.', (size % MB).ToString().Substring(0, 2), " MB");
}
}
return s_size;
}
В результате мы получили вот такое удобное и простое приложение: