Как реализовать временные таблицы и иерархические группировки в C# и SQL (аналоги функционала 1С)?

Программист IT и автоматизация бизнеса
← На главную

При переходе с платформы 1С:Предприятие на стек технологий .NET (C#) разработчики часто сталкиваются с отсутствием привычных высокоуровневых абстракций, таких как МенеджерВременныхТаблиц или метод Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам). В мире C# и классических SQL-баз данных эти задачи решаются на более низком уровне, что дает гибкость, но требует четкого понимания архитектуры данных. Разберем по шагам, как эмулировать привычный функционал 1С средствами SQL Server и Entity Framework.

Реализация временных таблиц: от 1С к SQL-сессиям

В 1С временная таблица живет внутри объекта Запрос, пока существует его менеджер. В C#, используя ADO.NET или Entity Framework Core, мы работаем напрямую с сессией базы данных. Рассмотрим ситуацию, когда нам нужно создать промежуточный набор данных для сложного отчета. Проанализируем два основных способа:

  1. Локальные временные таблицы (#Table): Они создаются в базе данных tempdb и видны только в рамках текущего открытого соединения SqlConnection.
  2. Common Table Expressions (CTE): Обобщенные табличные выражения позволяют логически выделить кусок кода внутри одного запроса. Это максимально близко к виртуальным таблицам запроса в 1С.

Рассмотрим пример использования временной таблицы через EF Core. Нам необходимо убедиться, что соединение открыто вручную, иначе сессия закроется, и таблица исчезнет:


using (var context = new MyDbContext())
{
    context.Database.OpenConnection(); // Важно сохранить сессию
    
    // Создаем временную таблицу
    context.Database.ExecuteSqlRaw("CREATE TABLE #TempData (Id INT, Val DECIMAL(18,2))");
    
    // Наполняем ее данными
    context.Database.ExecuteSqlRaw("INSERT INTO #TempData (Id, Val) VALUES (1, 100.50)");
    
    // Используем в основном запросе
    var result = context.DataRecords
        .FromSqlRaw("SELECT * FROM MyTable WHERE Id IN (SELECT Id FROM #TempData)")
        .ToList();
}

Для передачи больших объемов данных из C# во временную таблицу SQL лучше всего использовать Table-Valued Parameters (TVP). Это позволяет передать объект DataTable прямо в параметр хранимой процедуры, что работает гораздо быстрее, чем серия инсертов.

Иерархические группировки и итоги (Аналог "ИТОГИ ПО")

Одной из самых мощных функций 1С является секция ИТОГИ. В SQL Server для этих целей используются операторы ROLLUP и GROUPING SETS. Они позволяют получить в одном результате запроса и детальные записи, и строки промежуточных итогов. Выясним причину их эффективности: база данных делает агрегацию за один проход, что экономит ресурсы.

Посмотрим на пример SQL-запроса, который формирует иерархию "Организация - Подразделение - Сумма":


SELECT 
    OrganizationId, 
    DepartmentId, 
    SUM(Amount) as TotalAmount,
    GROUPING(DepartmentId) as IsSubTotal -- Флаг, указывающий, что это строка итога
FROM Sales
GROUP BY ROLLUP (OrganizationId, DepartmentId);

В результате мы получим таблицу, где строки с NULL в колонке DepartmentId (при IsSubTotal = 1) будут являться итогами по организации. Это позволяет строить отчеты-деревья без лишних циклов в коде C#.

Алгоритм обхода результата по группировкам на стороне C#

Если данные уже получены в виде плоского списка, нам нужно превратить их в дерево для вывода в интерфейс (например, в WPF или ASP.NET). Проанализируем алгоритм построения дерева из плоской таблицы с использованием ILookup. Этот интерфейс похож на словарь, но позволяет хранить коллекцию значений для одного ключа, что идеально подходит для связи "Родитель -> Дети".

Разберем пример реализации:


public class ReportRow
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Name { get; set; }
    public decimal Value { get; set; }
}

// Допустим, мы получили список rows из БД
var lookup = rows.ToLookup(r => r.ParentId);

// Рекурсивная функция для обхода (аналог обхода по группировкам)
void ProcessLevel(int? parentId, int level)
{
    foreach (var item in lookup[parentId])
    {
        Console.WriteLine(new string('-', level) + item.Name + ": " + item.Value);
        ProcessLevel(item.Id, level + 1); // Уходим на следующий уровень группировки
    }
}

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

Построение кросс-таблиц (Matrix reports)

Кросс-таблицы (где колонки формируются динамически на основе данных) в 1С создаются через СКД. В C# у нас есть несколько путей. Рассмотрим вариант трансформации данных с помощью LINQ. Выясним, как преобразовать плоский список продаж в таблицу "Товар / Месяц".

Проанализируем ситуацию: у нас есть данные вида { Product, Month, Sum }. Нам нужно получить объект, где свойствами будут названия месяцев. Самый гибкий способ — использование словаря для хранения динамических колонок:


var crossData = rows
    .GroupBy(r => r.Product)
    .Select(g => new {
        ProductName = g.Key,
        MonthlyValues = g.ToDictionary(x => x.Month, x => x.Sum)
    });

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

Специализированные движки отчетов (Альтернатива СКД)

Если задача стоит в создании сложных печатных форм, не стоит изобретать велосипед. Существуют бесплатные и open-source решения, которые по функционалу близки к СКД:

  1. FastReport Open Source: Позволяет визуально проектировать отчеты, поддерживает иерархии, вложенные отчеты и сложные группировки. Это практически полный аналог конструктора макетов 1С.
  2. Microsoft Report Viewer (RDLC): Классика для .NET. Поддерживает концепцию Tablix, которая умеет автоматически расширяться и вглубь (группировки строк), и вширь (группировки колонок — кросс-таблицы).
  3. QuestPDF: Современный подход, где отчет описывается кодом. Это удобно для программистов, так как позволяет использовать все преимущества C# (типизацию, рефакторинг) при проектировании сложных бланков.

Подведем итог: Для эффективной работы в C# нам нужно перестать искать "одну волшебную кнопку" и начать комбинировать инструменты. Используйте SQL ROLLUP для расчетов, LINQ Lookup для построения деревьев в памяти и FastReport для визуализации. Такой подход обеспечит производительность, сопоставимую с лучшими решениями на 1С, сохраняя при этом гибкость промышленного программирования.

← На главную