{"id":24183543,"url":"https://github.com/KlestovAlexej/Wattle.Examples","last_synced_at":"2025-09-21T05:32:18.981Z","repository":{"id":60771701,"uuid":"453645983","full_name":"KlestovAlexej/Wattle.Examples","owner":"KlestovAlexej","description":"Примеры использования Wattle","archived":false,"fork":false,"pushed_at":"2025-03-01T23:31:16.000Z","size":1550,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-09T09:51:27.626Z","etag":null,"topics":["array","async","batch-processing","cache","code-generation","domain-driven-design","domainobject","idempotency","idempotency-key","identity","inmemory-cache","lock","memory-cache","partitions","performance","postgresql","sql-server","telemetry","unitofwork","unlimited"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages?q=Acme.Wattle","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/KlestovAlexej.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-30T10:12:39.000Z","updated_at":"2025-04-10T09:04:35.000Z","dependencies_parsed_at":"2023-10-11T16:14:29.142Z","dependency_job_id":"cabbdfd9-3c07-43c9-9c29-bd8ca70b3d5c","html_url":"https://github.com/KlestovAlexej/Wattle.Examples","commit_stats":{"total_commits":380,"total_committers":2,"mean_commits":190.0,"dds":0.4184210526315789,"last_synced_commit":"a0965aab72db72daaf688798e14f7df6142caa59"},"previous_names":["klestovalexej/wattle3.examples"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/KlestovAlexej/Wattle.Examples","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KlestovAlexej%2FWattle.Examples","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KlestovAlexej%2FWattle.Examples/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KlestovAlexej%2FWattle.Examples/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KlestovAlexej%2FWattle.Examples/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KlestovAlexej","download_url":"https://codeload.github.com/KlestovAlexej/Wattle.Examples/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KlestovAlexej%2FWattle.Examples/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275867085,"owners_count":25542800,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-18T02:00:09.552Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["array","async","batch-processing","cache","code-generation","domain-driven-design","domainobject","idempotency","idempotency-key","identity","inmemory-cache","lock","memory-cache","partitions","performance","postgresql","sql-server","telemetry","unitofwork","unlimited"],"created_at":"2025-01-13T09:13:58.849Z","updated_at":"2025-09-21T05:32:18.662Z","avatar_url":"https://github.com/KlestovAlexej.png","language":"C#","readme":"# Wattle.Examples\n\nПримеры использования фреймворка для создания высокопроизводительных серверов с доменными объектами.\n\nФреймворк кроссплатформенный, написан 100% на [C#](https://ru.wikipedia.org/wiki/C_Sharp) под [.NET 9](https://dotnet.microsoft.com/en-us/download/dotnet/9.0).\n\nПакеты **nuget** начинаются с префикса [Acme.Wattle](https://www.nuget.org/packages?q=Acme.Wattle)\n\nПолнофункциональный [демонстрационный сервер](https://github.com/KlestovAlexej/Wattle.DemoServer) на базе библиотеки.\n\n# Содержание\n- [Как запускать примеры](#как-запускать-примеры)\n- [**Идемпотентность**](#идемпотентность)\n    - [Пример старта реестра в БД на **1.000.000.000** уникальных ключей в RAM за **17 секунд**](#пример-старта-реестра-на-1000000000-уникальных-ключей-1712-секунд)\n- [**Лок-объекты**](#лок-объекты)\n- [Контейнеры](#контейнеры)\n    - [**Массивы бесконечной длины**](#массивы-бесконечной-длины)\n- [**Пакетная обработка задач**](#пакетная-обработка-задач)\n- [Телеметрия приложения](#телеметрия-приложения)\n- [Доменные объекты](#доменные-объекты)\n    - [Паттерн **Unit of Work**](#паттерн-unit-of-work)\n- [Автогенерация мапперов на чистом ADO.NET для PostgreSQL и SQL Server](#автогенерация-мапперов-на-чистом-adonet)\n    - [Кодогенерация мапперов](#кодогенерация-мапперов)\n    - [Основные возможности](#основные-возможности)\n        - [**Генерация уникального** персистентного в БД значения **первичного ключа** с минимальным обращением к БД](#генерация-уникального-персистентного-в-бд-значения-первичного-ключа-с-минимальным-обращением-к-бд)\n            - [Результат параллельного создания **50.000.000** уникальных первичных ключей **(33,7 секунды)**](#результат-параллельного-создания-50000000-уникальных-первичных-ключей-337-секунды)\n        - [**Кэширование записей** по первичному ключу](#кэширование-записей-по-первичному-ключу)\n            - [Результат параллельного чтения **10.000.000** записей БД по первичному ключу **(9,3 секунд)**](#результат-параллельного-чтения-10000000-записей-бд-по-первичному-ключу-93-секунд)\n        - [Поддержка **партиционирования PostgreSQL** из коробки](#поддержка-партиционирования-postgresql-из-коробки)\n---\n## Как запускать примеры\nВсе примеры оформлены как [NUnit](https://nunit.org/)-тесты для запуска в ОС Windows из-под [Visual Studio 2022](https://visualstudio.microsoft.com/ru/vs/) (проверено на версии 17.8.1).\n\nВсе БД в примерах создаются и уничтожаются автоматически при запуске теста.\n\nПараметры подключения к серверу БД надо настроить в файле [DbCredentials.cs](/Common/DbCredentials.cs) и примеры 100% готовы к запуску.\n\nБазы данных на которых проверялись примеры [PostgreSQL (15.1 и 16.0)](https://www.postgresql.org/) и [SQL Server 2019 Standard](https://www.microsoft.com/ru-ru/sql-server/sql-server-2019).\n\n---\n## Идемпотентность\n\n[Идемпотентность](https://ru.wikipedia.org/wiki/%D0%98%D0%B4%D0%B5%D0%BC%D0%BF%D0%BE%D1%82%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C) поддерживается готовыми компонентами - *реестры уникальных ключей* из nuget пакета [Acme.Wattle.UniqueRegisters](https://www.nuget.org/packages/Acme.Wattle.UniqueRegisters/).\n\nРеестры уникальных ключей реализуют:\n- Хранение ключей в оперативной памяти и персистентно в БД.\n- Ключи могут быть ассоциированы с произвольными данными, тоже хранимыми в памяти.\n- Поиск ключа или данных по ключу происходит только в оперативной памяти и не задействует БД.\n- Параллельная регистрация одного и того же уникального ключа не приводит к задвоениям или рассинхронизациям с БД.\n- Есть сборка мусора - устаревшие ключи и данные автоматически удаляются из оперативной памяти по критерию заданным программистом.\n- Адаптирован для применения партиционирования PostgreSQL таблицы ключей для изятия устаревших ключей из памяти и БД.\n- Хранение ключей и данных в оперативной памяти оптимизировано и не нагружает сборщик мусора.\n- Ключи и данные в оперативной памяти занимают места *O(_Число ключей * (размер ключа + размер данных)_)* и не расходуют память на служебную информацию CLR.\n- Регистрация нового ключа задействует БД только в момент подтверждения [Unit of Work](https://martinfowler.com/eaaCatalog/unitOfWork.html).\n- Регистрация нового ключа отменяется автоматически в аварийных ситуациях работы Unit of Work.\n \nВ проекте [UniqueRegisters.Examples](https://github.com/KlestovAlexej/Wattle.Examples/tree/master/UniqueRegisters/Examples) весь код примеров.\n\n### Пример старта реестра на **1.000.000.000** уникальных ключей *(17,12 секунд)*\n\nВ данном примере в БД создано 1.000.000.000 условных ключей транзакци и полезной нагрузки.\n\u003cbr\u003e На данной БД запускается реестр уникальных ключей для загрузки всех ключей в память.\n\u003cbr\u003e\nКлюч : 16 байт [Guid](https://docs.microsoft.com/ru-ru/dotnet/api/system.guid?view=net-6.0)\n\u003cbr\u003eДанные : 8 байт [Long](https://docs.microsoft.com/ru-ru/dotnet/api/system.int64?view=net-6.0)\n\u003cbr\u003eТест [Example_Start](https://github.com/KlestovAlexej/Wattle.Examples/blob/master/UniqueRegisters/Examples/Examples.cs#L67) из примеров [Examples.cs](https://github.com/KlestovAlexej/Wattle.Examples/blob/master/UniqueRegisters/Examples/Examples.cs).\n| Действие    | Результат  |\n| --- | --- |\n| Время создания и инициализации реестра | 17,12 секунды |\n| Занято памяти | 24.000.662.440 байт |\n| Среднее время поиска данных по ключу | 16,92 микросекунды  |\n\u003cdetails\u003e\u003csummary\u003eПолный лог примера:\u003c/summary\u003e\n\n```\nТекущее время тестов : 24.11.2023 1:00:00\nНастройки сервера PostgreSQL (файл 'postgresql.conf').\nenable_partition_pruning = on # должно быть 'on'\n\n----------------------------------------------------------------------\nСоздание ключей в БД.\n\nВремя создания ключей : 02:13:07.2141639\nВсего ключей          : 1 000 000 000\n\nВсего занято памяти тестом : 24 004 924 304 байт\nЗанято памяти реестром     : 24 000 388 640 байт\n\nКоличество созданных Unit of Works : 0\n\nКоличество реальных подключений к БД : 16 745\nКоличество реальных транзакций БД    : 16 705\nКоличество сессий мапперов           : 16 745\n\nЧисло зарегестрированных ключей : 1 000 000 000\nЧисло регистраций ключей        : \nКоличество загрузок групп ключей из персистентного хранилища : 19\nКоличество сохранений групп ключей в персистентное хранилище : 1\n\nСодержимого файлового кэша для быстрого старта 'C:\\Dev\\Wattle.Examples\\UniqueRegisters\\Examples\\bin\\Release\\net9.0-windows\\win-x64\\Data\\TransactionKeys' (файлов 20) :\ngkeys_TransactionKeys_358.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_361.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_364.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_367.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_370.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_373.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_376.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_379.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_382.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_385.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_388.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_391.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_394.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_397.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_400.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_403.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_406.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_409.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_412.gkeys - размер 1 200 001 958\ngkeys_TransactionKeys_415.gkeys - размер 1 200 001 958\n\n----------------------------------------------------------------------\nСтарт реестра на '1 000 000 000' ключах в БД и файловым кэше.\nВремя создания и инициализации реестра : 00:00:17.1194485\n\nВремя поиска всех ключей                 : 04:41:56.4193987\nСреднее время поиска ключа (микросекунд) : 16,9164193987\nВсего занято памяти тестом : 24 005 521 040 байт\nЗанято памяти реестром     : 24 000 662 440 байт\nКоличество созданных Unit of Works : 1 000 000 000\nКоличество реальных подключений к БД : 1\nКоличество реальных транзакций БД    : 0\nКоличество сессий мапперов           : 1\nЧисло зарегестрированных ключей : 1 000 000 000\nЧисло регистраций ключей        : 0\nКоличество загрузок групп ключей из персистентного хранилища : 20\nКоличество сохранений групп ключей в персистентное хранилище : 0\n\n----------------------------------------------------------------------\nПолное время теста : 06:55:21.4239389\n```\n\n\u003c/details\u003e\n\nПараметры ПК :\u003cbr\u003e_OS Windows 11 Pro x64, CPU Intel Core i9-9900KS, RAM 48GB, SSD Samsung 970 Evo Plus 2Tb, DB PostgreSQL 16.0_\n\n---\n## Лок-объекты\n\nВ проекте [Locks](/Locks) весь код примеров.\n\u003cbr\u003e\n\u003cbr\u003e Лок-объекты позволяют получать монопольную блокировку используя для блокировки любой объект применимый в качестве ключа [Dictionary](https://learn.microsoft.com/ru-ru/dotnet/api/system.collections.generic.dictionary-2?view=net-7.0).\n\u003cbr\u003e\n\u003cbr\u003e К примеру, на C# так блокировку получить нельзя ([описание проблемы](https://stackoverflow.com/questions/1329919/why-lockinteger-var-is-not-allowed-but-monitor-enterinteger-var-allowed)):\n\n```csharp\nlock(123)\n{\n    ...\n}\n```\n\u003cbr\u003e Лок-объекты позволяют это делать :\n\n```csharp\nusing var lockObject = locks.GetLock(123);\nif (lockObject.TryEnter())\n{\n    ...\n}\n```\n\u003cbr\u003e Лок-объекты позволяют получать монопольную блокировку используя конструкцию [await](https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/operators/await) :\n\n```csharp\n\nusing var lockObject = locks.GetLock(123);\nif (await lockObject.TryEnterAsync(cancellationToken))\n{\n    ...\n}\n```\n\n---\n## Контейнеры\n\nВ проекте [Containers](/Containers) весь код примеров.\n\n---\n### Массивы бесконечной длины\n\nМассивы бесконечной длины позволяют:\n- Cоздавать массивы (любых элементов) неограниченной длинны, на **всю доступную** оперативную память.\n- Добавлять дополнительную память массиву без копирования памяти.\n\n---\nТест [Example_Byte_Compare](/Containers/ExamplesFlexIncrementArrayElements.cs#L87) из примеров [ExamplesFlexIncrementArrayElements.cs](/Containers/ExamplesFlexIncrementArrayElements.cs).\n\u003cbr\u003eСравнивает работу **массива бесконечной длины** с **[byte](https://learn.microsoft.com/ru-ru/dotnet/api/system.byte?view=net-7.0)[]**.\n\u003cdetails\u003e\u003csummary\u003eПолный лог теста :\u003c/summary\u003e\n\n```\nТест FlexIncrementArrayElements\n\nЭлементов массива      : 100 000 000\nВыделено памяти (байт) : 100 000 000\nВремя                  : 00:00:00.0042149\n\nВремя заполнения памяти : 00:00:00.2242565\n\nВремя проверки памяти : 00:00:20.1773304\nВремя проверки памяти : 00:01:28.1654554\n\n----------------------------------------------------------------------\n\nЭталонный тест byte[]\n\nЭлементов массива      : 100 000 000\nВыделено памяти (байт) : 100 000 000\nВремя                  : 00:00:00.0001068\n\nВремя заполнения памяти : 00:00:00.0519848\n\nВремя проверки памяти : 00:00:22.0954823\nВремя проверки памяти : 00:01:27.0711296\n```\n\n\u003c/details\u003e\n\n---\nТест [Example_Byte_Expand](/Containers/ExamplesFlexIncrementArrayElements.cs#L31) из примеров [ExamplesFlexIncrementArrayElements.cs](/Containers/ExamplesFlexIncrementArrayElements.cs).\n\u003cbr\u003eCоздание массив из **30.000.000.000** элементов типа [byte](https://learn.microsoft.com/ru-ru/dotnet/api/system.byte?view=net-7.0).\n\u003cdetails\u003e\u003csummary\u003eПолный лог теста :\u003c/summary\u003e\n\n```\nЭлементов массива типа byte : 30 000 000 000\nВыделено памяти (байт)      : 30 001 001 616\nВремя                       : 00:00:01.1994014\n```\n\n\u003c/details\u003e\n\nПараметры ПК :\u003cbr\u003e_OS Windows 11 Pro x64, CPU Intel Core i9-9900KS, RAM 48GB, SSD Samsung 970 Evo Plus 2Tb_\n\n---\n## Пакетная обработка задач\nКласс **BaseBatchingTasksProcessor** позволяет реализовать стратегию пакетной обработки задач.\n\u003cbr/\u003e\u003cbr/\u003eВ проекте [BatchingTasks](/BatchingTasks) весь код примеров.\n\u003cbr/\u003e\u003cbr/\u003e**Пример проблемы и её решение :**\n\u003cbr/\u003eЕсть сервер, который принимает множество подключений.\n\u003cbr/\u003eКаждое принятое подключение исполняет задачу на сервера, к примеру делает однотипный [INSERT](https://postgrespro.ru/docs/postgresql/9.6/sql-insert) в БД.\n\u003cbr/\u003eЛогично делать [INSERT](https://postgrespro.ru/docs/postgresql/9.6/sql-insert) в БД не штучно, а использовать [BULK INSERT](https://www.postgresql.org/docs/current/sql-copy.html) для большей производительности вставки.\n\u003cbr/\u003eВозникает проблема общения задач всех подключений в единый [BULK INSERT](https://www.postgresql.org/docs/current/sql-copy.html).\n\u003cbr/\u003eПомимо этого, каждое подключение должно получить уведомление что задача выполнена.\n\u003cbr/\u003e\u003cbr/\u003eРешением проблемы занимается класс **BaseBatchingTasksProcessor**, его возможности :\n- Алгоритм обработки пакета задач польностью определяет программист.\n- Принимать для обработки объекты-задачи и автоматически объединять их в пакеты для обработки.\n- Обработка задач в пакете идёт асинхронно.\n- Есть возможность ожидать конца исполнения задачи или вообще не ждать конца исполнения задачи.\n- Ожидание конца исполнения задачи может быть с использованием конструкции [await](https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/operators/await).\n- Если при обработке пакета была ошибка, она будет доведена до всех ожидающих конца исполнения задачи.\n- Количество задач в пакете можно ограничить\n- Количество исполняемых пакетов можно ограничить\n- Время ожидания выполнения задачи можно выбрать произвольное или ждать бесконечно\n\u003cbr/\u003e\u003cbr/\u003eПример добавления задачи и ожидание конца её исполнения:\n```csharp\nusing var processor = new BatchingTasksProcessor(...);\n\nvar task =\n    new BatchingTask\n    {\n        ...\n    };\n\nvar waitHandle = processor.Process(task);\nwaitHandle.Wait();\n```\n\n---\n## Телеметрия приложения\n\nПростая публикация и доступ по REST-интерфейсу к произвольной телеметрии приложения.\n\nЭто не замена и не конкурент [OpenTelemetry](https://opentelemetry.io/).\n\nВ проекте [InfrastructureMonitoring](/InfrastructureMonitoring) весь код примера.\n\n---\nКлаcc приложения с телеметрией для публикации по REST-интерфейсу:\n```csharp\npublic class CustomClassA\n{\n    ...\n\n    // Телеметрия.\n    public long Count;\n}\n```\n\n\n[Swagger](https://swagger.io/) документация REST-интерфейса доступа к телеметрии приложения:\n```csharp\nhttp://localhost:5601/swagger/index.html\n```\n\n---\nПолучение значение телеметрии через REST-интерфейс используя C# и готовый клиент:\n```csharp\nusing var client = new InfrastructureMonitorClient(\"localhost\", 5601);\n\nvar snapshot = client.GetInfrastructureMonitorSnapshot(WellknownCustomInfrastructureMonitors.CustomClassA);\nvar snapShotValue = snapshot.Values.Single(v =\u003e v.Id == WellknownCustomSnapShotInfrastructureMonitorValues.CustomClassA.Count);\nvar count = (long)snapShotValue.Data.Value;\n\nConsole.WriteLine(count);\n```\n\n---\nПолучение значение телеметрии через REST-интерфейс используя PowerShell:\n```ps1\n# Идентификатор объекта приложения с телеметрией - WellknownCustomInfrastructureMonitors.CustomClassA\n$MonitorId = \"153C867D-A122-44BB-B689-949FB8C61B00\"\n\n# Идентификатор значения с телеметрией - WellknownCustomSnapShotInfrastructureMonitorValues.CustomClassA.Count\n$ValueId = \"50FF7F28-582B-4297-93EE-323FB812880F\"\n\n$Result = Invoke-WebRequest -Uri \"http://localhost:5601/api/1/InfrastructureMonitor/GetInfrastructureMonitorSnapshotValue?monitorId=$MonitorId\u0026valueId=$ValueId\" -Method Get\n\nWrite-Host ($Result)\n```\n\nПолученное значение телеметрии:\n```json\n{\n  \"Id\": \"153c867d-a122-44bb-b689-949fb8c61b00\",\n  \"Name\": \"Класс CustomClassA\",\n  \"Description\": \"Класс CustomClassA\",\n  \"SnapShotTime\": \"2022-01-31T15:12:48.6279777+03:00\",\n  \"Value\": {\n    \"Id\": \"50ff7f28-582b-4297-93ee-323fb812880f\",\n    \"Name\": \"Количество элементов\",\n    \"Description\": \"Количество элементов\",\n    \"Data\": {\n      \"Value\": 11,\n      \"Type\": \"long\"\n    },\n    \"Group\": null,\n    \"Tags\": [\n      \"COUNT\"\n    ]\n  }\n}\n```\n\n---\n## Доменные объекты\nФреймворк позволяет программировать [доменные объекты](https://en.wikipedia.org/wiki/Business_object) и максимально сосредоточиться на бизнес-значимых понятиях не в ущерб производительности.\n\nВ проекте [DomainObjects](/DomainObjects) весь код примеров.\n\n---\n### Паттерн Unit of Work\nРабота с доменными объектами идёт в пределах [Unit of Work](https://martinfowler.com/eaaCatalog/unitOfWork.html) с контраком *IUnitOfWork*.\nСам Unit of Work создаётся точкой входа в доменную область с контраком *IEntryPoint*.\n\nВесь код примеров в файле [Examples.cs](DomainObjects/Examples/Examples.cs).\n\n\u003cdetails\u003e\u003csummary\u003eКонтракт IEntryPoint :\u003c/summary\u003e\n\n```csharp\n/// \u003csummary\u003e\n/// Точка входа в доменную область.\n/// \u003c/summary\u003e\npublic interface IEntryPoint : IDisposable, IDrivenObject\n{\n    /// \u003csummary\u003e\n    /// Монитор инфраструктуры.\n    /// \u003c/summary\u003e\n    new IInfrastructureMonitorEntryPoint InfrastructureMonitor { get; }\n\n    /// \u003csummary\u003e\n    /// Провайдер экземпляра \u003csee cref=\"IUnitOfWork\"/\u003e для вызывающего потока.\n    /// \u003c/summary\u003e\n    IUnitOfWorkProvider UnitOfWorkProvider { get; }\n\n    /// \u003csummary\u003e\n    /// Создание \u003csee cref=\"IUnitOfWork\"/\u003e для вызывающего потока. \n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"context\"\u003eКонекст сосздания \u003csee cref=\"IUnitOfWork\"/\u003e.\u003c/param\u003e\n    /// \u003creturns\u003eСозданный \u003csee cref=\"IUnitOfWork\"/\u003e.\u003c/returns\u003e\n    /// \u003cexception cref=\"InternalException\"\u003eЕсли для вызывающего потока уже определён \u003csee cref=\"IUnitOfWork\"/\u003e.\u003c/exception\u003e\n    IUnitOfWork CreateUnitOfWork(object context = null);\n\n    /// \u003csummary\u003e\n    /// Создание \u003csee cref=\"IUnitOfWork\"/\u003e для вызывающего потока. \n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"context\"\u003eКонекст сосздания \u003csee cref=\"IUnitOfWork\"/\u003e.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003eСозданный \u003csee cref=\"IUnitOfWork\"/\u003e.\u003c/returns\u003e\n    /// \u003cexception cref=\"InternalException\"\u003eЕсли для вызывающего потока уже определён \u003csee cref=\"IUnitOfWork\"/\u003e.\u003c/exception\u003e\n    ValueTask\u003cIUnitOfWork\u003e CreateUnitOfWorkAsync(object context = null, CancellationToken cancellationToken = default);\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eКонтракт IUnitOfWork :\u003c/summary\u003e\n\n```csharp\n/// \u003csummary\u003e\n/// Сессия доменной области.\n/// \u003c/summary\u003e\npublic interface IUnitOfWork : IHostMappersSession\n{\n    /// \u003csummary\u003e\n    /// Признак уничтожения \u003csee cref=\"IUnitOfWork\"/\u003e.\n    /// \u003c/summary\u003e\n    bool IsDisposed { get; }\n\n    /// \u003csummary\u003e\n    /// Статус \u003csee cref=\"IUnitOfWork\"/\u003e.\n    /// \u003c/summary\u003e\n    UnitOfWorkState State { get; }\n\n    /// \u003csummary\u003e\n    /// Произвольные ассоциированные данные.\n    /// \u003c/summary\u003e\n    IDictionary\u003cGuid, object\u003e Tags { get; }\n\n    /// \u003csummary\u003e\n    /// Точка входа в доменную область.\n    /// \u003c/summary\u003e\n    IEntryPoint EntryPoint { get; }\n\n    /// \u003csummary\u003e\n    /// Реест локальных изменяемых состояний доменных объектов использованных в \u003csee cref=\"IUnitOfWork\"/\u003e. \n    /// \u003c/summary\u003e\n    IDomainObjectMutableStatesRegister DomainObjectMutableStates { get; }\n\n    /// \u003csummary\u003e\n    /// Реестры доменных объектов. \n    /// \u003c/summary\u003e\n    IDomainObjectRegisters Registers { get; }\n\n    /// \u003csummary\u003e\n    /// Стратегия проверки успешности завершения \u003csee cref=\"IUnitOfWork\"/\u003e.\n    /// \u003c/summary\u003e\n    IUnitOfWorkCommitVerifying CommitVerifying { get; set; }\n\n    /// \u003csummary\u003e\n    /// Стратегия проверки успешности завершения \u003csee cref=\"IUnitOfWork\"/\u003e определена.\n    /// \u003c/summary\u003e\n    bool IsDefinedCommitVerifying { get; }\n\n    /// \u003csummary\u003e\n    /// Добавить доменное поведение в \u003csee cref=\"IUnitOfWork\"/\u003e.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"domainBehaviour\"\u003eДоменное поведение.\u003c/param\u003e\n    void AddBehaviour(IDomainBehaviour domainBehaviour);\n\n    /// \u003csummary\u003e\n    /// Добавить новый доменный объект в \u003csee cref=\"IUnitOfWork\"/\u003e (\u003csee cref=\"IDomainObject.GetData\"/\u003e, \u003csee cref=\"DomainObjectDataTarget.Create\"/\u003e).\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"domainObject\"\u003eДоменный объект.\u003c/param\u003e\n    void AddNew(IDomainObject domainObject);\n\n    /// \u003csummary\u003e\n    /// Добавить изменённый доменный объект в \u003csee cref=\"IUnitOfWork\"/\u003e (\u003csee cref=\"IDomainObject.GetData\"/\u003e, \u003csee cref=\"DomainObjectDataTarget.Update\"/\u003e).\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"domainObject\"\u003eДоменный объект.\u003c/param\u003e\n    void AddUpdate(IDomainObject domainObject);\n\n    /// \u003csummary\u003e\n    /// Добавить удалённый доменный объект в \u003csee cref=\"IUnitOfWork\"/\u003e (\u003csee cref=\"IDomainObject.GetData\"/\u003e, \u003csee cref=\"DomainObjectDataTarget.Delete\"/\u003e).\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"domainObject\"\u003eДоменный объект.\u003c/param\u003e\n    void AddDelete(IDomainObject domainObject);\n\n    /// \u003csummary\u003e\n    /// Добавить проверку верси данных доменного объект в \u003csee cref=\"IUnitOfWork\"/\u003e (\u003csee cref=\"IDomainObject.GetData\"/\u003e, \u003csee cref=\"DomainObjectDataTarget.Version\"/\u003e).\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"domainObject\"\u003eДоменный объект.\u003c/param\u003e\n    void AddVersion(IDomainObject domainObject);\n\n    /// \u003csummary\u003e\n    /// Подтвердить \u003csee cref=\"IUnitOfWork\"/\u003e.\n    /// Вызов может быть только один.\n    /// \u003c/summary\u003e\n    /// \u003creturns\u003e\u003csee langword=\"true\" /\u003e - если были изменения. \u003csee langword=\"false\" /\u003e - если изменений не было или \u003csee cref=\"IUnitOfWork\"/\u003e был отменён.\u003c/returns\u003e\n    bool Commit();\n\n    /// \u003csummary\u003e\n    /// Подтвердить \u003csee cref=\"IUnitOfWork\"/\u003e.\n    /// Вызов может быть только один.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003e\u003csee langword=\"true\" /\u003e - если были изменения. \u003csee langword=\"false\" /\u003e - если изменений не было или \u003csee cref=\"IUnitOfWork\"/\u003e был отменён.\u003c/returns\u003e\n    ValueTask\u003cbool\u003e CommitAsync(CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Отменить \u003csee cref=\"IUnitOfWork\"/\u003e.\n    /// Вызывать можно неограниченное количество раз.\n    /// \u003c/summary\u003e\n    void Rollback();\n\n    /// \u003csummary\u003e\n    /// Пометить \u003csee cref=\"IUnitOfWork\"/\u003e как подозрительный для принудительного восстановления всех доменных объектов задействованных в \u003csee cref=\"IUnitOfWork\"/\u003e.\n    /// Вызывать можно неограниченное количество раз.\n    /// \u003c/summary\u003e\n    void Suspect();\n}\n```\n\n\u003c/details\u003e\n\nПример создания доменного объекта :\n```csharp\nIEntryPoint entryPoint;\n\n...\n\nusing (IUnitOfWork unitOfWork = entryPoint.CreateUnitOfWork())\n{\n    var register = unitOfWork.Registers.GetRegister\u003cIDomainObjectRegisterDocument\u003e();\n    var instance = register.New(new DateTime(2022, 1, 2, 3, 4, 5, 6), 1002, 444);\n\n    unitOfWork.Commit();\n}\n```\n\n---\n## Автогенерация мапперов на чистом ADO.NET\n\nПримеры автогенерённых ADO.NET мапперов для [PostgreSQL](/Mappers/PostgreSql/Implements/).\n\nПримеры автогенерённых ADO.NET мапперов для [SQL Server](/Mappers/SqlServer/Implements/).\n\n---\n### Определение для кодогенератора мапперов\n\nВесь код примера [WellknownDomainObjectFields.cs](/Mappers/PostgreSql/Common/WellknownDomainObjectFields.cs).\n\n\u003cdetails\u003e\u003csummary\u003eПример определения структуры записи БД и параметров маппера :\u003c/summary\u003e\n\n```csharp\n[Description(\"Объект Object_A\")]\n[SchemaMapper(MapperId = WellknownDomainObjects.Text.Object_A, IsPrepared = true, IsCached = true, DeleteMode = SchemaMapperDeleteMode.Delete)]\n[SchemaMapperIdentityFieldPostgreSql(PartitionsLevel = ComplexIdentity.Level.L1)]\n[SchemaMapperIdentityField(DbSequenceName = \"Sequence_%ObjectName%\")]\n[SchemaMapperRevisionField(IsVersion = true)]\npublic static class Object_A\n{\n    [Description(\"Дата-время (DateTime). Обновляемое поле.\")]\n    [SchemaMapperField(typeof(DateTime), Where = true, Order = true, UpdateMode = SchemaMapperFieldUpdateMode.UpdateDirect)]\n    public static readonly Guid Value_DateTime = new(\"DCE071BB-796E-4397-91B8-EAF116747880\");\n\n    [Description(\"Дата-время (DateTime). Не обновляемое поле.\")]\n    [SchemaMapperField(typeof(DateTime), Where = true, Order = true, UpdateMode = SchemaMapperFieldUpdateMode.NotUpdate)]\n    public static readonly Guid Value_DateTime_NotUpdate = new(\"273A65E2-7647-42DB-A15D-58B69A64C69D\");\n\n    [Description(\"Число (long). Поле обновляется только при изменении значения.\")]\n    [SchemaMapperField(typeof(long), Where = true, Order = true, UpdateMode = SchemaMapperFieldUpdateMode.Update)]\n    public static readonly Guid Value_Long = new(\"87A005ED-CA51-4C60-83EC-6540AC0823D6\");\n\n    [Description(\"Число с поддержкой null (int?). Обновляемое поле.\")]\n    [SchemaMapperField(typeof(int?), Where = true, Order = true, UpdateMode = SchemaMapperFieldUpdateMode.UpdateDirect)]\n    public static readonly Guid Value_Int = new(\"198251EF-8183-4A09-A760-E5BAAFBBB6FF\");\n\n    [Description(\"Строка без ограничения размера с поддержкой null. Поле обновляется только при изменении значения.\")]\n    [SchemaMapperField(typeof(string), DbIsNull = true, UpdateMode = SchemaMapperFieldUpdateMode.Update)]\n    public static readonly Guid Value_String = new(\"100E6573-B387-4CB5-B3D6-45DF4CB2CC9C\");\n}\n```\n\n\u003c/details\u003e\n\nВесь скрипт [SqlScript.sql](/Mappers/PostgreSql/Common/SqlScripts/SqlScript.sql).\n\n\u003cdetails\u003e\u003csummary\u003eSQL-скрипт создания таблицы и последовательности PostgreSQL :\u003c/summary\u003e\n\n```sql\nCREATE SEQUENCE Sequence_Object_A START WITH 1 INCREMENT BY 1;\n\nCREATE TABLE object_a(\n  id bigint NOT NULL,\n  revision bigint NOT NULL,\n  value_datetime timestamp NOT NULL,\n  value_long bigint NOT NULL,\n  value_int integer,\n  value_string text,\n  value_datetime_notupdate timestamp NOT NULL,\n  PRIMARY KEY(id)\n) PARTITION BY RANGE (id);\n```\n\n\u003c/details\u003e\n\n---\n### Создание XML-схемы мапперов по определению\n\nВесь код примера [DbMappersSchemaXmlBuilder.cs](/Mappers/PostgreSql/Common/DbMappersSchemaXmlBuilder.cs).\n\n\u003cdetails\u003e\u003csummary\u003eПример создания XML-схемы маппераPostgreSQL :\u003c/summary\u003e\n\n```csharp\nvar mappers =\n    new SchemaMappers\n    {\n        Storage = SchemaMapperStorage.PostgreSql,\n        CodeGeneration =\n            new SchemaMappersCodeGeneration\n            {\n                MappersCommonNamespaceName = \"Acme.Wattle.Examples.Mappers.PostgreSql.Implements.Generated.Common\",\n                MappersIntefacesNamespaceName = \"Acme.Wattle.Examples.Mappers.PostgreSql.Implements.Generated.Interface\",\n                MappersImplementsNamespaceName = \"Acme.Wattle.Examples.Mappers.PostgreSql.Implements.Generated.Implements\",\n                MappersTestsNamespaceName = \"Acme.Wattle.Examples.Mappers.PostgreSql.Implements.Generated.Tests\",\n                UnitTestCategoryName = TestCategory.Unit,\n                UnitTestTimeout = TestTimeout.Unit,\n            },\n    };\n\nvar schemaModel =\n    new SchemaModel\n    {\n        Description = $\"Генератор XML модели тут : {GetType().FullName}\",\n        Mappers = new List\u003cSchemaMappers\u003e { mappers }\n    };\n\nvar type = typeof(Object_A);\n\nvar schemaMapperBuilder = SchemaMapperBuilder\n    .New()\n    .SetSchema(mappers.Storage)\n    .Configure(type);\nvar schemaMapper = schemaMapperBuilder.CreateSchema(mappers.Storage);\nmappers.Mappers.Add(schemaMapper);\n\nvar xml = schemaModel.ToXml();\n\nvar fileName = Path.Combine(ProviderProjectBasePath.ProjectPath, @\"Mappers\\PostgreSql\\Implements\\DbMappers.Schema.xml\");\nFile.WriteAllText(fileName, xml);\n```\n\n\u003c/details\u003e\n\n---\n### Кодогенерация мапперов\n\nВесь примера в файле [Mappers.PostgreSql.Implements.csproj](/Mappers/PostgreSql/Implements/Mappers.PostgreSql.Implements.csproj).\n\n\u003cdetails\u003e\u003csummary\u003eПример проектного файла :\u003c/summary\u003e\n\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\"\u003e\n\n\t...\n\t\n\t\u003cItemGroup\u003e\n\t\t\u003cAdditionalFiles Include=\"DbMappers.Schema.xml\" /\u003e\n\t\u003c/ItemGroup\u003e\n\n\t\u003cItemGroup\u003e\n\t\t\u003cPackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.0.0\" /\u003e\n\t\t\u003cPackageReference Include=\"Npgsql\" Version=\"6.0.3\" /\u003e\n\t\t\u003cPackageReference Include=\"NUnit3TestAdapter\" Version=\"4.2.1\" /\u003e\n\t\t\u003cPackageReference Include=\"Acme.Wattle.CodeGeneration.Generators\" Version=\"3.0.0.35768\" /\u003e\n\t\t\u003cPackageReference Include=\"Acme.Wattle.Common\" Version=\"3.0.0.35768\" /\u003e\n\t\t\u003cPackageReference Include=\"Acme.Wattle.Mappers\" Version=\"3.0.0.35768\" /\u003e\n\t\t\u003cPackageReference Include=\"Acme.Wattle.Mappers.PostgreSql\" Version=\"3.0.0.35768\" /\u003e\n\t\t\u003cPackageReference Include=\"Acme.Wattle.Testing\" Version=\"3.0.0.35768\" /\u003e\n\t\t\u003cPackageReference Include=\"Acme.Wattle.Testing.Databases.PostgreSql\" Version=\"3.0.0.35768\" /\u003e\n\n\t\t\u003c!-- Кодогенератор общих определений для мапперов --\u003e\n\t\t\u003cPackageReference Include=\"Acme.Wattle.CodeGeneration.Generator.Common\" Version=\"3.0.0.35768\"\u003e\n\t\t\t\u003cPrivateAssets\u003eall\u003c/PrivateAssets\u003e\n\t\t\t\u003cIncludeAssets\u003eruntime; build; native; contentfiles; analyzers; buildtransitive\u003c/IncludeAssets\u003e\n\t\t\u003c/PackageReference\u003e\n\n\t\t\u003c!-- Кодогенератор интерфейсов мапперов --\u003e\n\t\t\u003cPackageReference Include=\"Acme.Wattle.CodeGeneration.Generator.Interfaces\" Version=\"3.0.0.35768\"\u003e\n\t\t\t\u003cPrivateAssets\u003eall\u003c/PrivateAssets\u003e\n\t\t\t\u003cIncludeAssets\u003eruntime; build; native; contentfiles; analyzers; buildtransitive\u003c/IncludeAssets\u003e\n\t\t\u003c/PackageReference\u003e\n\n\t\t\u003c!-- Кодогенератор реализаций мапперов --\u003e\n\t\t\u003cPackageReference Include=\"Acme.Wattle.CodeGeneration.Generator.Implements\" Version=\"3.0.0.35768\"\u003e\n\t\t\t\u003cPrivateAssets\u003eall\u003c/PrivateAssets\u003e\n\t\t\t\u003cIncludeAssets\u003eruntime; build; native; contentfiles; analyzers; buildtransitive\u003c/IncludeAssets\u003e\n\t\t\u003c/PackageReference\u003e\n\n\t\t\u003c!-- Кодогенератор автоматических NUnit-тестов мапперов --\u003e\n\t\t\u003cPackageReference Include=\"Acme.Wattle.CodeGeneration.Generator.Tests\" Version=\"3.0.0.35768\"\u003e\n\t\t\t\u003cPrivateAssets\u003eall\u003c/PrivateAssets\u003e\n\t\t\t\u003cIncludeAssets\u003eruntime; build; native; contentfiles; analyzers; buildtransitive\u003c/IncludeAssets\u003e\n\t\t\u003c/PackageReference\u003e\n\t\u003c/ItemGroup\u003e\n\n\u003c/Project\u003e\n```\n\n\u003c/details\u003e\n\nВесь примера в файле [DbMappers.Interfaces.Generated.cs](/Mappers/PostgreSql/Implements/GeneratedFiles/Acme.Wattle.CodeGeneration.Generator.Interfaces/Acme.Wattle.CodeGeneration.Generator.Interfaces.SourceGenerator/DbMappers.Interfaces.Generated.cs).\n\n\u003cdetails\u003e\u003csummary\u003eПример сгенерированного интерфейса маппера :\u003c/summary\u003e\n\n```csharp\n/// \u003csummary\u003e\n/// Объект с партиционированием таблицы БД, первичным ключём из последовательности БД, с оптимистической конкуренцией на уровне БД, с кешированием записей БД в памяти на уровне маппера\n/// \u003c/summary\u003e\n[MapperInterface(WellknownMappersAsText.Object_A)]\n[SuppressMessage(\"ReSharper\", \"UnusedMember.Global\")]\n// ReSharper disable once PartialTypeWithSinglePart\npublic partial interface IMapperObject_A : IMapper\n{\n    /// \u003csummary\u003e\n    /// Имя таблицы БД.\n    /// \u003c/summary\u003e\n    string TableName { get; }\n\n    /// \u003csummary\u003e\n    /// Получение следующего значения идентити из последовательности \"Sequence_Object_A\".\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает следующего значение идентити.\u003c/returns\u003e\n    long GetNextId(IMappersSession session);\n\n    /// \u003csummary\u003e\n    /// Получение следующих значений идентити из последовательности \"Sequence_Object_A\".\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"count\"\u003eКоличество следующийх значений идентити из последовательности.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eКокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает коллекцию следующих значений идентити.\u003c/returns\u003e\n    IList\u003clong\u003e GetNextIds(IMappersSession session, int count, CancellationToken cancellationToken);\n\n    /// \u003csummary\u003e\n    /// Получение следующего значения идентити из последовательности \"Sequence_Object_A\".\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает следующего значение идентити.\u003c/returns\u003e\n    ValueTask\u003clong\u003e GetNextIdAsync(IMappersSession session, CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Проверка существования записис с указаным идентити.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"id\"\u003eИдентити записи.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает \u003csee langword=\"true\" /\u003e если запись существует иначе если запись не существует возвращает \u003csee langword=\"false\" /\u003e.\u003c/returns\u003e\n    bool Exists(IMappersSession session, long id);\n\n    /// \u003csummary\u003e\n    /// Проверка существования записис с указаным идентити.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"id\"\u003eИдентити записи.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает \u003csee langword=\"true\" /\u003e если запись существует иначе если запись не существует возвращает \u003csee langword=\"false\" /\u003e.\u003c/returns\u003e\n    ValueTask\u003cbool\u003e ExistsAsync(IMappersSession session, long id, CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Проверка существования записис с указаным идентити.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"id\"\u003eИдентити записи.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает \u003csee langword=\"true\" /\u003e если запись существует иначе если запись не существует возвращает \u003csee langword=\"false\" /\u003e.\u003c/returns\u003e\n    bool ExistsRaw(IMappersSession session, long id);\n\n    /// \u003csummary\u003e\n    /// Проверка существования записис с указаным идентити.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"id\"\u003eИдентити записи.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает \u003csee langword=\"true\" /\u003e если запись существует иначе если запись не существует возвращает \u003csee langword=\"false\" /\u003e.\u003c/returns\u003e\n    ValueTask\u003cbool\u003e ExistsRawAsync(IMappersSession session, long id, CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Проверка существования записис с указаным идентити и указаной версией данных.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"id\"\u003eИдентити записи.\u003c/param\u003e\n    /// \u003cparam name=\"revision\"\u003eОжидаемая версия данных записи.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает \u003csee langword=\"true\" /\u003e если запись существует иначе если запись не существует возвращает \u003csee langword=\"false\" /\u003e.\u003c/returns\u003e\n    bool ExistsRevision(IMappersSession session, long id, long revision);\n\n    /// \u003csummary\u003e\n    /// Проверка существования записис с указаным идентити и указаной версией данных.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"id\"\u003eИдентити записи.\u003c/param\u003e\n    /// \u003cparam name=\"revision\"\u003eОжидаемая версия данных записи.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает \u003csee langword=\"true\" /\u003e если запись существует иначе если запись не существует возвращает \u003csee langword=\"false\" /\u003e.\u003c/returns\u003e\n    ValueTask\u003cbool\u003e ExistsRevisionAsync(IMappersSession session, long id, long revision, CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Получить запись с указаным идентити.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"mappersSession\"\u003eХост сессии БД.\u003c/param\u003e\n    /// \u003cparam name=\"id\"\u003eИдентити записи.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает значение если запись существует иначе если запись не существует возвращает \u003csee langword=\"null\" /\u003e.\u003c/returns\u003e\n    Object_ADtoActual Get(IHostMappersSession mappersSession, long id);\n\n    /// \u003csummary\u003e\n    /// Получить запись с указаным идентити.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"mappersSession\"\u003eХост сессии БД.\u003c/param\u003e\n    /// \u003cparam name=\"id\"\u003eИдентити записи.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает значение если запись существует иначе если запись не существует возвращает \u003csee langword=\"null\" /\u003e.\u003c/returns\u003e\n    ValueTask\u003cIMapperDto\u003e GetAsync(IHostMappersSession mappersSession, long id, CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Получить запись с указаным идентити.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"id\"\u003eИдентити записи.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает значение если запись существует иначе если запись не существует возвращает \u003csee langword=\"null\" /\u003e.\u003c/returns\u003e\n    Object_ADtoActual GetRaw(IMappersSession session, long id);\n\n    /// \u003csummary\u003e\n    /// Получить запись с указаным идентити.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"id\"\u003eИдентити записи.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает значение если запись существует иначе если запись не существует возвращает \u003csee langword=\"null\" /\u003e.\u003c/returns\u003e\n    ValueTask\u003cObject_ADtoActual\u003e GetRawAsync(IMappersSession session, long id, CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Обновить запись.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"data\"\u003eИзмененная запись.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает актуальное состояние записи.\u003c/returns\u003e\n    Object_ADtoActual Update(IMappersSession session, Object_ADtoChanged data);\n\n    /// \u003csummary\u003e\n    /// Обновить запись.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"data\"\u003eИзмененная запись.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает актуальное состояние записи.\u003c/returns\u003e\n    ValueTask\u003cIMapperDto\u003e UpdateAsync(IMappersSession session, Object_ADtoChanged data, CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Массовое создание записей.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"data\"\u003eЗаписи.\u003c/param\u003e\n    void BulkInsert(IMappersSession session, IEnumerable\u003cObject_ADtoNew\u003e data);\n\n    /// \u003csummary\u003e\n    /// Массовое создание записей.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"data\"\u003eЗаписи.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    ValueTask BulkInsertAsync(IMappersSession session, IEnumerable\u003cObject_ADtoNew\u003e data, CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Создать запись.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"data\"\u003eНовая запись.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает актуальное состояние записи.\u003c/returns\u003e\n    Object_ADtoActual New(IMappersSession session, Object_ADtoNew data);\n\n    /// \u003csummary\u003e\n    /// Создать запись.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"data\"\u003eНовая запись.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает актуальное состояние записи.\u003c/returns\u003e\n    ValueTask\u003cIMapperDto\u003e NewAsync(IMappersSession session, Object_ADtoNew data, CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Удаление записи.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"data\"\u003eДанные достаточные для удаления записи.\u003c/param\u003e\n    void Delete(IMappersSession session, Object_ADtoDeleted data);\n\n    /// \u003csummary\u003e\n    /// Удаление записи.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"data\"\u003eДанные достаточные для удаления записи.\u003c/param\u003e\n    /// \u003cparam name=\"cancellationToken\"\u003eТокен отмены.\u003c/param\u003e\n    ValueTask DeleteAsync(IMappersSession session, Object_ADtoDeleted data, CancellationToken cancellationToken = default);\n\n    /// \u003csummary\u003e\n    /// Получить итератор всех записей выбранных с учётом фильтра.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"selectFilter\"\u003eФильтр выбора записий. Если указан \u003csee langword=\"null\" /\u003e то выбираются все записи.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает итератор всех выбраных записей.\u003c/returns\u003e\n    IEnumerable\u003cObject_ADtoActual\u003e GetEnumerator(IMappersSession session, IMapperSelectFilter selectFilter = null);\n\n    /// \u003csummary\u003e\n    /// Получить итератор всех записей выбранных с учётом фильтра.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"selectFilter\"\u003eФильтр выбора записий. Если указан \u003csee langword=\"null\" /\u003e то выбираются все записи.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает итератор всех выбраных записей.\u003c/returns\u003e\n    IEnumerable\u003cObject_ADtoActual\u003e GetEnumeratorRaw(IMappersSession session, IMapperSelectFilter selectFilter = null);\n\n    /// \u003csummary\u003e\n    /// Получить итератор записей выбранных с учётом фильтра для заданной страницы указанного размера.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"pageIndex\"\u003eИндекс выбираемой страницы. Первая страница имеет индекс 1.\u003c/param\u003e\n    /// \u003cparam name=\"pageSize\"\u003eРазмер страницы. Минимальный размер страницы 1. Максимальный размер страницы 1000.\u003c/param\u003e\n    /// \u003cparam name=\"selectFilter\"\u003eФильтр выбора записий. Если указан \u003csee langword=\"null\" /\u003e то выбираются все записи.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает итератор всех выбраных записей.\u003c/returns\u003e\n    IEnumerable\u003cObject_ADtoActual\u003e GetEnumeratorPage(IMappersSession session, int pageIndex, int pageSize, IMapperSelectFilter selectFilter = null);\n\n    /// \u003csummary\u003e\n    /// Получить итератор идентити записей выбранных с учётом фильтра для заданной страницы указанного размера.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"pageIndex\"\u003eИндекс выбираемой страницы. Первая страница имеет индекс 1.\u003c/param\u003e\n    /// \u003cparam name=\"pageSize\"\u003eРазмер страницы. Минимальный размер страницы 1. Максимальный размер страницы 1000.\u003c/param\u003e\n    /// \u003cparam name=\"selectFilter\"\u003eФильтр выбора записий. Если указан \u003csee langword=\"null\" /\u003e то выбираются все записи.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает итератор всех выбраных идентити записей.\u003c/returns\u003e\n    IEnumerable\u003clong\u003e GetEnumeratorIdentitiesPage(IMappersSession session, int pageIndex, int pageSize, IMapperSelectFilter selectFilter = null);\n\n    /// \u003csummary\u003e\n    /// Получить количество записей удовлетворяющих фильтру выборки.\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"session\"\u003eСессия БД.\u003c/param\u003e\n    /// \u003cparam name=\"selectFilter\"\u003eФильтр выбора записий. Если указан \u003csee langword=\"null\" /\u003e то выбираются все записи.\u003c/param\u003e\n    /// \u003creturns\u003eВозвращает количество записей удовлетворяющих фильтру выборки.\u003c/returns\u003e\n    long GetCount(IMappersSession session, IMapperSelectFilter selectFilter = null);\n}\n```\n\n\u003c/details\u003e\n\n---\n### Основные возможности\n\nДля PostgreSQL в файле [Examples.cs](/Mappers/PostgreSql/Implements/Examples.cs) весь код примеров работы с мапперами.\n\nДля SQL Server в файле [Examples.cs](/Mappers/SqlServer/Implements/Examples.cs) весь код примеров работы с мапперами.\n\n---\n#### Генерация уникального персистентного в БД значения первичного ключа с минимальным обращением к БД\n\nГенератор уникальных первичных ключей работает на базе последовательностей БД.\n\nГенератор позволяет получать уникальные значения без необходимости реального обращения к БД в момент генерации.\n\nВесь код примеров в тесте [Example_IdentityCache_Parallel](Mappers/PostgreSql/Implements/Examples.cs#L1086).\n\n\u003cdetails\u003e\u003csummary\u003eПример параллельного создания 50.000.000 уникальных первичных ключей :\u003c/summary\u003e\n\n```csharp\nconst int CacheSize = 100_000;\nusing var identityCache = CreateIdentityCache(CacheSize);\n\nvar identites = new HashSet\u003clong\u003e();\nParallel.For(0, 500 * CacheSize,\n    _ =\u003e\n    {\n        using var mappersSession = m_mappers.OpenSession();\n\n        var identity = identityCache.GetNextIdentity(mappersSession);\n\n        lock (identites)\n        {\n            Assert.IsFalse(identites.Contains(identity));\n            identites.Add(identity);\n        }\n\n        mappersSession.Commit();\n    });\n```\n\n\u003c/details\u003e\n\n##### Результат параллельного создания **50.000.000** уникальных первичных ключей *(33,7 секунды)*\n\n| Действие    | Результат  |\n| --- | --- |\n| Время работы | 33.7 секунды |\n| Количество реальных подключений к БД | 2 |\n| Количество идентити |  50.000.000 | \n| Количество идентити полученных из кэша в памяти (без обращения к БД) |  49.990.310 | \n| Количество идентити полученных из БД |  9.690 | \n| Количество реальных подключений к БД |  10.454 | \n| Количество сессий мапперов |  50.000.764 | \n\nПараметры ПК :\u003cbr\u003e_OS Windows 11 Pro x64, CPU Intel Core i9-9900KS, RAM 48GB, SSD Samsung 970 Evo Plus 2Tb, DB PostgreSQL 15.1_\n\n---\n#### Кэширование записей по первичному ключу\n\nКэширование записей по первичну ключу происходит автоматически (если это наcтроено для маппера).\n\nУ каждого маппера свой кэш.\n\nКэш обновляется и очищается автоматически при создании, чтении, обновлении или удалении записи.\n\nВесь код примеров в тесте [Example_Select_With_MemoryCache_Parallel](Mappers/PostgreSql/Implements/Examples.cs#L672).\n\n\u003cdetails\u003e\u003csummary\u003eПример параллельного чтения 10.000.000 записей БД по первичному ключу :\u003c/summary\u003e\n\n```csharp\nvar ids = new List\u003clong\u003e();\n\n...\n\n// Заполнение таблицы.\nusing (var mappersSession = m_mappers.OpenSession())\n{\n    foreach (var id in ids)\n    {\n        mapper.New(\n            mappersSession,\n            new Object_ADtoNew\n            {\n                Id = id,\n                Value_DateTime = DateTime.Now,\n                Value_DateTime_NotUpdate = DateTime.Now,\n                Value_Int = null,\n                Value_Long = id,\n                Value_String = $\"Text {id}\",\n            });\n    }\n\n    mappersSession.Commit();\n}\n\n// Выборка записей по первичному ключу.\nParallel.For(0, 10_000_000,\n    _ =\u003e\n    {\n        using var mappersSession = m_mappers.CreateHostMappersSession();\n\n        var id = ProviderRandomValues.GetItem(ids);\n        var dto = mapper.Get(mappersSession, id);\n        Assert.IsNotNull(dto);\n    });\n```\n\n\u003c/details\u003e\n\n##### Результат параллельного чтения **10.000.000** записей БД по первичному ключу *(9,3 секунд)*\n\n| Действие    | Результат  |\n| --- | --- |\n| Время работы | 9,3 секунды |\n| Количество реальных подключений к БД | 2 |\n| Количество сессий мапперов | 2 |\n| Количество объектов в памяти |  1.000 | \n| Количество поисков объектов в памяти |  10.000.000 | \n| Количество найденных объектов в памяти |  10.000.000 | \n\nПараметры ПК :\u003cbr\u003e_OS Windows 11 Pro x64, CPU Intel Core i9-9900KS, RAM 48GB, SSD Samsung 970 Evo Plus 2Tb, DB PostgreSQL 15.1_\n\n---\n#### Поддержка партиционирования PostgreSQL из коробки\n\nМапперы для PostgreSQL имеют готовый компонент управлениями партициями (если это настроено для маппера) таблицы которую они обслуживают.\n\n\u003cdetails\u003e\u003csummary\u003eПример создания партиции и записи в неё :\u003c/summary\u003e\n\n```csharp\nvar mappers = ServiceProviderHolder.Instance.GetRequiredService\u003cIMappers\u003e();\nvar mapper = (MapperObject_A)mappers.GetMapper\u003cIMapperObject_A\u003e();\n\nvar partitionId = 67;\n\n// Создание партиции таблицы.\nusing (var mappersSession = mappers.OpenSession())\n{\n    mapper.Partitions.CreatePartition(mappersSession, partitionId, partitionId + 1);\n\n    mappersSession.Commit();\n}\n\nusing (var mappersSession = mappers.OpenSession())\n{\n    // Запись в партицию таблицы.\n    mapper.New(\n        mappersSession,\n        new Object_ADtoNew\n        {\n            Id = ComplexIdentity.Build(mapper.Partitions.Level, partitionId, 1),\n            Value_DateTime = DateTime.Now,\n            Value_DateTime_NotUpdate = DateTime.Now,\n            Value_Int = null,\n            Value_Long = 314,\n            Value_String = \"Text\",\n        });\n\n    mappersSession.Commit();\n}\n```\n\n\u003c/details\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FKlestovAlexej%2FWattle.Examples","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FKlestovAlexej%2FWattle.Examples","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FKlestovAlexej%2FWattle.Examples/lists"}