Время прочтения: 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. Проверка заполненности полей.
  2. Подсвечивание незаполненных полей.
  3. Проверка формата даты.
  4. Ограничение размера загружаемого файла.
  5. Очистка всех полей кнопкой.
  6. Автоматическое получение размера файла удобном формате.

Код данных фишек представлен ниже в порядке их упоминания (Методы 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;
        }

В результате мы получили вот такое удобное и простое приложение: