При переходе с платформы 1С:Предприятие на стек технологий .NET (C#) разработчики часто сталкиваются с отсутствием привычных высокоуровневых абстракций, таких как МенеджерВременныхТаблиц или метод Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам). В мире C# и классических SQL-баз данных эти задачи решаются на более низком уровне, что дает гибкость, но требует четкого понимания архитектуры данных. Разберем по шагам, как эмулировать привычный функционал 1С средствами SQL Server и Entity Framework.
В 1С временная таблица живет внутри объекта Запрос, пока существует его менеджер. В C#, используя ADO.NET или Entity Framework Core, мы работаем напрямую с сессией базы данных. Рассмотрим ситуацию, когда нам нужно создать промежуточный набор данных для сложного отчета. Проанализируем два основных способа:
tempdb и видны только в рамках текущего открытого соединения SqlConnection.Рассмотрим пример использования временной таблицы через 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#.
Если данные уже получены в виде плоского списка, нам нужно превратить их в дерево для вывода в интерфейс (например, в 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С, когда мы сначала выводим итог по группе, а затем детальные записи внутри нее.
Кросс-таблицы (где колонки формируются динамически на основе данных) в 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 решения, которые по функционалу близки к СКД:
Tablix, которая умеет автоматически расширяться и вглубь (группировки строк), и вширь (группировки колонок — кросс-таблицы).Подведем итог: Для эффективной работы в C# нам нужно перестать искать "одну волшебную кнопку" и начать комбинировать инструменты. Используйте SQL ROLLUP для расчетов, LINQ Lookup для построения деревьев в памяти и FastReport для визуализации. Такой подход обеспечит производительность, сопоставимую с лучшими решениями на 1С, сохраняя при этом гибкость промышленного программирования.