Строго типизированная работа с SQL-ем напрямую без LINQ
От: Alexander Polyakov  
Дата: 10.11.10 23:49
Оценка:
Хочется работать с SQL-ем напрямую, и в тоже время хочется статической проверки sql statement-ов, параметров и результатов запросов. Intellisense тоже подтянется, если будет алгоритм статической проверки.

Собственно, в чем сложность? Если параметризованный sql statement известен в дизайн тайме, то упомянутая статическая проверка это лишь техническая проблема. Например, при использовании LINQtoSQL или Entity Framework можно запрос оформить в виде хранимой процедуры и сгенерировать строго типизированную обертку на C#-е. Правда, тут есть небольшое неудобство -- в хранимой процедуре надо вручную указывать типы параметров. Но это неудобство можно устранить, см. Приложение 1. Таким образом, со статическим SQL-ем проблем нет -- наше желание реализуемо.

Но потребности реальных проектов не укладываются в статический SQL. Причины бывают самые разнообразные.

Есть большое подозрение, что всю или почти всю динамичность, требуемую в реальных проектах, можно описывать следующим образом. Динамичность sql statement-а задается некоторым набором параметров, заданных на “конечных” множествах: Boolean, Enum, интерфейс с известным числом реализаций (вложенность конструкторов разрешается произвольная). Некоторая утилита (в post-build event-е) перебирает все комбинации таких параметров и генерирует sql statement-ы. Сгенерированные sql statement-ы проходят проверку. Еще динамичность может зависеть от массива неизвестной длинны, в этом случае в дизайн тайме будет генерироваться sql statement-ы для массива с Length=1,2,3,5, надо же где-то остановиться . Последнее выглядит немного странным и похожим на кривизну, но при детальном рассмотрении выясняется, что кривизны нет.

Продемонстрирую описанную схему на вот этом примере
Автор: Alexander Polyakov
Дата: 02.11.10
.

Создаем Preprocessed Text Template с названием PartLinkQueryProvider (IntelliSense и Syntax Coloring обеспечивает T4 Editor):


Дописываем partial класс к PartLinkQueryProvider и пишем вот такой код:
    public partial class PartLinkQueryProvider : IQueryProvider<PartLinkInfo>
    {
        public readonly IParamManager<PartLinkParams> PartLinkParams;
        private readonly IInnerPartLinkSwitcher innerPartLinkSwitcher;

        public PartLinkQueryProvider(
            IParamManager<PartLinkParams> partLinkParams, IInnerPartLinkSwitcher innerPartLinkSwitcher)
        {
            PartLinkParams = partLinkParams;
            this.innerPartLinkSwitcher = innerPartLinkSwitcher;
        }
    }

    public partial class PartLinkInfo
    {
    }

    public partial class PartLinkParams
    {
    }

    public interface IInnerPartLinkSwitcher
    {
        void AddSubQuery(Action<bool> action);
    }

    public class WithoutInnerPartLinkSwitcher : IInnerPartLinkSwitcher
    {
        public void AddSubQuery(Action<bool> action)
        {
            //no-op
        }
    }

    public class WithInnerPartLinkSwitcher : IInnerPartLinkSwitcher
    {
        public readonly IParamManager<InnerPartLinkParams> InnerPartLinkParams;
        private readonly bool latestPublicationOnly;

        public WithInnerPartLinkSwitcher(
            IParamManager<InnerPartLinkParams> innerPartLinkParams, bool latestPublicationOnly)
        {
            InnerPartLinkParams = innerPartLinkParams;
            this.latestPublicationOnly = latestPublicationOnly;
        }

        public void AddSubQuery(Action<bool> action)
        {
            action(latestPublicationOnly);
        }
    }

    public partial class InnerPartLinkParams
    {
    }
Запускаем build. В post build event-е запускается некоторая утилита. Утилита перебирает все классы, реализующие интерфейс IQueryProvider<T>. Утилита указывает на ошибки, если такие имеются. Если все хорошо, генерируются следующий код:
    public partial class PartLinkParams
    {
        public readonly Guid GenericServicePartsPageID;
        public readonly Guid ModelPublicationID;
        public readonly Guid ServicePartsPageID;

        public PartLinkParams(Guid genericServicePartsPageID, Guid modelPublicationID, Guid servicePartsPageID)
        {
            GenericServicePartsPageID = genericServicePartsPageID;
            ModelPublicationID = modelPublicationID;
            ServicePartsPageID = servicePartsPageID;
        }
    }

    public partial class InnerPartLinkParams
    {
        public readonly Guid ModelID;
        public readonly string LanguageCode;

        public InnerPartLinkParams(Guid modelID, string languageCode)
        {
            ModelID = modelID;
            LanguageCode = languageCode;
        }
    }

    public partial class PartLinkInfo
    {
        public string PartNumber { get; set; }
    }

Теперь мы можем выполнять в строго типизированной манере три варианта запроса, упомянутые в условии задачи:
        public static void Main()
        {
            ExecutePartLinkQuery(
                dbCommand =>
                new PartLinkQueryProvider(
                    new PartLinkParams(
                        new Guid("..."),
                        new Guid("..."),
                        new Guid("...")
                        ).AsQueryParams(dbCommand),
                    new WithoutInnerPartLinkSwitcher()
                    )
                );

            ExecutePartLinkQuery(
                dbCommand =>
                new PartLinkQueryProvider(
                    new PartLinkParams(
                        new Guid("..."),
                        new Guid("..."),
                        new Guid("...")
                        ).AsQueryParams(dbCommand),
                    new WithInnerPartLinkSwitcher(
                        new InnerPartLinkParams(
                            new Guid("..."),
                            "en"
                            ).AsQueryParams(dbCommand),
                        false
                        )
                    )
                );

            ExecutePartLinkQuery(
                dbCommand =>
                new PartLinkQueryProvider(
                    new PartLinkParams(
                        new Guid("..."),
                        new Guid("..."),
                        new Guid("...")
                        ).AsQueryParams(dbCommand),
                    new WithInnerPartLinkSwitcher(
                        new InnerPartLinkParams(
                            new Guid("..."),
                            "en"
                            ).AsQueryParams(dbCommand),
                        true
                        )
                    )
                );
        }

        private static void ExecutePartLinkQuery(
            Func<DbCommand, IQueryProvider<PartLinkInfo>> queryProviderFunc)
        {
            var dbCommand = ...
            var queryProvider = queryProviderFunc(dbCommand);

            queryProvider.FillDbCommand(dbCommand);

            using (var reader = dbCommand.ExecuteReader())
            {
                while (reader.Read())
                {
                    PartLinkInfo partLinkInfo = queryProvider.Materialize(reader);
                    Console.WriteLine(partLinkInfo.PartNumber);
                }
            }
        }


Материализацию может осуществлять как самописная инфраструктура, так и метод ObjectContext.Translate из Entity Framework.


Приложение 1.
Sql statement кладем в некоторое место (xml, cs, и т.д.), откуда его можно достать в дизайн тайме. Специальная утилита в дизайн тайме достает sql statement, объявляет все параметры типа binary и смотрит план запроса. В плане запроса будет соответствующий оператор неявного преобразования из binary в нужный тип .
Re: Строго типизированная работа с SQL-ем напрямую без LINQ
От: IT Россия linq2db.com
Дата: 11.11.10 01:10
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Создаем Preprocessed Text Template с названием PartLinkQueryProvider (IntelliSense и Syntax Coloring обеспечивает T4 Editor):


Почему таким же способом не сгенерировать различные выражения Linq для различных вариантов?
Если нам не помогут, то мы тоже никого не пощадим.
Re[2]: Строго типизированная работа с SQL-ем напрямую без LI
От: Alexander Polyakov  
Дата: 11.11.10 19:16
Оценка:
Здравствуйте, IT, Вы писали:

AP>>Создаем Preprocessed Text Template с названием PartLinkQueryProvider (IntelliSense и Syntax Coloring обеспечивает T4 Editor):

IT>Почему таким же способом не сгенерировать различные выражения Linq для различных вариантов?
Да, пожалуйста, можно и Linq генерировать. Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно .
Re[3]: Строго типизированная работа с SQL-ем напрямую без LI
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 11.11.10 19:36
Оценка: +1
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Здравствуйте, IT, Вы писали:


AP>>>Создаем Preprocessed Text Template с названием PartLinkQueryProvider (IntelliSense и Syntax Coloring обеспечивает T4 Editor):

IT>>Почему таким же способом не сгенерировать различные выражения Linq для различных вариантов?
AP>Да, пожалуйста, можно и Linq генерировать. Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно .

Вот сколько тебя не читаю у тебя всегда сильно свое мнение, которые ты навязываешь окружающим.

Рассказываю тайну. Главный козырь Linq не столько в статической проверке как таковой сколько в динамической сборке запроса (композиция-декомпозиция) с проверкой корректности при компиляции.

Этот паттерн называется QueryObject и фаулером описан был давно. Linq реализует этот паттерн и добавляет к нему статическую проверку.
Статическую проверку в post-build можно и для обычных строк сделать, если они не клеятся динамически. Такое вполне работает в некоторых IDE для java.
Re[4]: Строго типизированная работа с SQL-ем напрямую без LI
От: Alexander Polyakov  
Дата: 11.11.10 23:29
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Статическую проверку в post-build можно и для обычных строк сделать, если они не клеятся динамически.

Ты не уловил сути первого поста. Там ровно про это! В первом посте как раз про динамическое склеивание (можно просто StringBuilder-ом), и при этом работает static checking.
Re[3]: Строго типизированная работа с SQL-ем напрямую без LI
От: IT Россия linq2db.com
Дата: 12.11.10 00:17
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>>>Создаем Preprocessed Text Template с названием PartLinkQueryProvider (IntelliSense и Syntax Coloring обеспечивает T4 Editor):

IT>>Почему таким же способом не сгенерировать различные выражения Linq для различных вариантов?
AP>Да, пожалуйста, можно и Linq генерировать. Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно .

Почему это побит. Ты же потом это компилировать будешь.
Если нам не помогут, то мы тоже никого не пощадим.
Re[5]: Строго типизированная работа с SQL-ем напрямую без LI
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 12.11.10 08:09
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Здравствуйте, gandjustas, Вы писали:


G>>Статическую проверку в post-build можно и для обычных строк сделать, если они не клеятся динамически.

AP>Ты не уловил сути первого поста. Там ровно про это! В первом посте как раз про динамическое склеивание (можно просто StringBuilder-ом), и при этом работает static checking.

Нет, ты заранее определяешь некоторый набор строк, это далеко не тоже самое что динамическое создание запросов с проверкой при компиляции.

Результат примерно одинаковый а степень удобства очень разная.
Re[4]: Строго типизированная работа с SQL-ем напрямую без LI
От: Alexander Polyakov  
Дата: 12.11.10 16:10
Оценка:
Здравствуйте, IT, Вы писали:

AP>>... Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно .

IT>Почему это побит. Ты же потом это компилировать будешь.
Не совсем понимаю о чем ты, что плохого в компиляции?
Если придираться к словам, то фразу “главный козырь побит” можно заменить на “у другого решения такая фича (static checking) теперь тоже имеется”.
Re[5]: Строго типизированная работа с SQL-ем напрямую без LI
От: IT Россия linq2db.com
Дата: 12.11.10 16:35
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>>>... Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно .

IT>>Почему это побит. Ты же потом это компилировать будешь.
AP>Не совсем понимаю о чем ты, что плохого в компиляции?
AP>Если придираться к словам, то фразу “главный козырь побит” можно заменить на “у другого решения такая фича (static checking) теперь тоже имеется”.

А... в этом смысле. Ты его через SQL прогоняешь?
Если нам не помогут, то мы тоже никого не пощадим.
Re[3]: Строго типизированная работа с SQL-ем напрямую без LI
От: 0x7be СССР  
Дата: 12.11.10 16:42
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Да, пожалуйста, можно и Linq генерировать. Но факт в том, что главный козырь Linq (static checking) побит. Соответственно, выбор между LINQ и SQL следует делать с учетом этого факта. Расклад поменялся, и притом существенно .

И что из этого следует? Какие есть плюсы и минусы у описанного подхода по сравнению с linq?
Re[6]: Строго типизированная работа с SQL-ем напрямую без LI
От: Alexander Polyakov  
Дата: 12.11.10 16:48
Оценка:
Здравствуйте, IT, Вы писали:

IT>... Ты его через SQL прогоняешь?

Да, проверка sql statement-ов осуществляется по базе через sql server.
Re[6]: Строго типизированная работа с SQL-ем напрямую без LI
От: Alexander Polyakov  
Дата: 12.11.10 17:01
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>... ты заранее определяешь некоторый набор строк ...

Нет, не верно. Программист не задает набор строк, он пишет код, который динамически формирует строки.

G>... динамическое создание запросов с проверкой при компиляции.

На эту тему тоже есть варианты, например, классический QueryObject и LINQ имеют сильные различие в этом плане. QueryObject предоставляет полную динамичность в отличие от LINQ. Ты, похоже, этого не понимаешь. Доказывать тебе что-либо выходит сильно дорого
Автор: Alexander Polyakov
Дата: 27.06.10
. У меня нет столько времени. Подсказка: expression tree в LINQ формирует компилятор, а в случае QueryObject компилятор не вмешивается в процесс формирования ET, все делается программистом (обычным кодом).

G>Результат примерно одинаковый а степень удобства очень разная.

Да, верно. Одно из различий, влияющее на степень удобства: с SQL СУБД часто бывает удобнее работать на ее родном языке, без посредников.
Re[7]: Строго типизированная работа с SQL-ем напрямую без LI
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 13.11.10 02:29
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Здравствуйте, gandjustas, Вы писали:


G>>... динамическое создание запросов с проверкой при компиляции.

AP>На эту тему тоже есть варианты, например, классический QueryObject и LINQ имеют сильные различие в этом плане. QueryObject предоставляет полную динамичность в отличие от LINQ. Ты, похоже, этого не понимаешь.
Это ты не понимаешь.

AP>Подсказка: expression tree в LINQ формирует компилятор, а в случае QueryObject компилятор не вмешивается в процесс формирования ET, все делается программистом (обычным кодом).

А что мне мешает руками создавать ET? Компилятор только во многом упрощает жизнь, но отнюдь не лишает возможностей.

G>>Результат примерно одинаковый а степень удобства очень разная.

AP>Да, верно. Одно из различий, влияющее на степень удобства: с SQL СУБД часто бывает удобнее работать на ее родном языке, без посредников.
Тут ты сильно неправ. В SQL нету средств декомпозиции. Я писал Linq запросы, которые в SQL давали в 5-6 раз больший выхлоп после оптимизации текста. В linq я разбил запрос на кусочки, потом из них собрал нужный мне результат.
Re[8]: Строго типизированная работа с SQL-ем напрямую без LI
От: Alexander Polyakov  
Дата: 13.11.10 10:26
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Это ты не понимаешь.

Не конструктивно. С какой целью пишешь такие неконструктивные вещи?

G>А что мне мешает руками создавать ET? Компилятор только во многом упрощает жизнь, но отнюдь не лишает возможностей.

Так речь и идет об “упрощении жизни”. Любую программу (в том числе формирование ET) можно написать в виде последовательности нулей и единиц. Возможность есть всегда.

G>В SQL нету средств декомпозиции.

Средства декомпозиции есть в T4 и в StringBuilder-е (можно и Razor попробовать).

G>В SQL нету средств декомпозиции.

В SQL изначально есть замкнутость: результат запроса может участвовать в качестве источника для другого запроса (подзапроса).

G>... В linq я разбил запрос на кусочки, потом из них собрал нужный мне результат.

Строку можно собирать хоть посимвольно.
Re[9]: Строго типизированная работа с SQL-ем напрямую без LI
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 13.11.10 14:16
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

G>>В SQL нету средств декомпозиции.

AP>Средства декомпозиции есть в T4 и в StringBuilder-е (можно и Razor попробовать).
Тем не менее это текстовая генерация запросов, со всеми вытекающими.
Статическая проверка при компиляции для определенного заранее набора запросов.

G>>В SQL нету средств декомпозиции.

AP>В SQL изначально есть замкнутость: результат запроса может участвовать в качестве источника для другого запроса (подзапроса).
Это тут причем?

G>>... В linq я разбил запрос на кусочки, потом из них собрал нужный мне результат.

AP>Строку можно собирать хоть посимвольно.
Ага, только гарантировать что-либо при этом невозможно.
Re[10]: Строго типизированная работа с SQL-ем напрямую без L
От: Alexander Polyakov  
Дата: 13.11.10 14:48
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>>>В SQL нету средств декомпозиции.

AP>>Средства декомпозиции есть в T4 и в StringBuilder-е (можно и Razor попробовать).
G>Тем не менее это текстовая генерация запросов, со всеми вытекающими.
Это текстовая генерация запросов со статической проверкой при компиляции, со всеми вытекающими удобствами.

G>Статическая проверка при компиляции для определенного заранее набора запросов.

И что?

G>>>В SQL нету средств декомпозиции.

AP>>В SQL изначально есть замкнутость: результат запроса может участвовать в качестве источника для другого запроса (подзапроса).
G>Это тут причем?
С учетом возможности декомпозиции строк это обеспечивает соответствующую декомпозицию запросов.

G>Ага, только гарантировать что-либо при этом невозможно.

Как раз возможна статическая проверка, которая гарантирует валидность запросов.
Re[11]: Строго типизированная работа с SQL-ем напрямую без L
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 13.11.10 15:02
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Здравствуйте, gandjustas, Вы писали:


G>>>>В SQL нету средств декомпозиции.

AP>>>Средства декомпозиции есть в T4 и в StringBuilder-е (можно и Razor попробовать).
G>>Тем не менее это текстовая генерация запросов, со всеми вытекающими.
AP>Это текстовая генерация запросов со статической проверкой при компиляции, со всеми вытекающими удобствами.
"Текстовая" и "удобства" — взаимоисключающие параграфы.

G>>Статическая проверка при компиляции для определенного заранее набора запросов.

AP>И что?
И что что это по сути не дает возможностей декомпозиции, за счет которой Linq рулит.

G>>>>В SQL нету средств декомпозиции.

AP>>>В SQL изначально есть замкнутость: результат запроса может участвовать в качестве источника для другого запроса (подзапроса).
G>>Это тут причем?
AP>С учетом возможности декомпозиции строк это обеспечивает соответствующую декомпозицию запросов.
"Декомпозиция строк" надо записать.
Как ты сделаешь проекции на строках?

G>>Ага, только гарантировать что-либо при этом невозможно.

AP>Как раз возможна статическая проверка, которая гарантирует валидность запросов.
Для динамически склеиваемых в рантайме — нет.
То что ты предлагаешь — автоматизировать процесс генерации набора запросов и проверять их. Но это далеко не дотягивает до Linq.
Re[12]: Строго типизированная работа с SQL-ем напрямую без L
От: Alexander Polyakov  
Дата: 13.11.10 16:20
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>"Текстовая" и "удобства" — взаимоисключающие параграфы.

Глупость, в реальности не так однозначно. Обратный пример: Web Forms -- попытка несколько удалиться от текста -- слила MVC, которая ближе к тексту.

G>>>Статическая проверка при компиляции для определенного заранее набора запросов.

AP>>И что?
G>И что что это по сути не дает возможностей декомпозиции, за счет которой Linq рулит.
Выше я уже писал, что декомпозиция присутствует.

G> "Декомпозиция строк" надо записать.

G>Как ты сделаешь проекции на строках?
SELECT
    A
FROM
    (<#= Table1 #>) Table1

<#+ string Table1 { get { return ToString(() => { #>
SELECT
    A,
    B
FROM
    Table1
<#+ });}} #>


G>Для динамически склеиваемых в рантайме — нет.

Строки склеиваются динамически в рантайме, да.

G>... Но это далеко не дотягивает до Linq.

Давай посмотрим, что до чего не дотягивает, приведи хотя бы схематично решение примера из первого поста.
Re[13]: Строго типизированная работа с SQL-ем напрямую без L
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 13.11.10 16:47
Оценка:
Здравствуйте, Alexander Polyakov, Вы писали:

AP>Здравствуйте, gandjustas, Вы писали:


G>>"Текстовая" и "удобства" — взаимоисключающие параграфы.

AP>Глупость, в реальности не так однозначно. Обратный пример: Web Forms -- попытка несколько удалиться от текста -- слила MVC, которая ближе к тексту.
Уходишь от темы.

G>>>>Статическая проверка при компиляции для определенного заранее набора запросов.

AP>>>И что?
G>>И что что это по сути не дает возможностей декомпозиции, за счет которой Linq рулит.
AP>Выше я уже писал, что декомпозиция присутствует.
Надо бы доказать.

G>> "Декомпозиция строк" надо записать.

G>>Как ты сделаешь проекции на строках?
AP>
AP>SELECT
AP>    A
AP>FROM
AP>    (<#= Table1 #>) Table1

AP><#+ string Table1 { get { return ToString(() => { #>
AP>SELECT
AP>    A,
AP>    B
AP>FROM
AP>    Table1
AP><#+ });}} #>
AP>


И что? Возможностей композиции я тут не вижу. Напрмиер я беру запрос X и накладываю на него свою проекцию, а в базу уходит только выбор необходимых полей, с учетом derived table, подзапросов и тому подобной фигни.

G>>Для динамически склеиваемых в рантайме — нет.

AP>Строки склеиваются динамически в рантайме, да.
Ну давай сначала. Покажи код, который в post-compile time сможет проверить корректоность запроса, если он клеится из кусков строк в Runtime.

G>>... Но это далеко не дотягивает до Linq.

AP>Давай посмотрим, что до чего не дотягивает, приведи хотя бы схематично решение примера из первого поста.
Честно говоря лениво реверсить SQL. Уверен что в Linq можно сделать тоже самое, если не упрется в ограничения провайдера.

Вот сценарий для Linq:

var query = GetQuery();
if(A)
{
    query = query.Where(...);
}
if(B)
{
    if(C)
    {
        query = SomeProcessQuery(query);
    }
    else
    {
        query = OtherProcessQuery(query);
    }
}


Если использовать Linq то в конце процедуры у нас гарантированно корректный запрос.
Если клеим строки то гарантировать что-либо практически невозможно.
Re[14]: Строго типизированная работа с SQL-ем напрямую без L
От: Alexander Polyakov  
Дата: 13.11.10 18:11
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>И что? Возможностей композиции я тут не вижу. Напрмиер я беру запрос X и накладываю на него свою проекцию, а в базу уходит только выбор необходимых полей, с учетом derived table, подзапросов и тому подобной фигни.

Напиши пример на linq, я его перепишу на T4.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.