{"id":481870,"date":"2026-06-01T09:33:57","date_gmt":"2026-06-01T09:33:57","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=481870"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=481870","title":{"rendered":"redb \u2014 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0434\u043b\u044f .NET \u043f\u043e\u0432\u0435\u0440\u0445 Postgres\/MSSQL: \u0431\u0435\u0437 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439, \u0431\u0435\u0437 Include, \u0441 \u043f\u043e\u043b\u043d\u044b\u043c LINQ"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/cd1\/c1f\/e3c\/cd1c1fe3cbc89378ed7f1539bf0941b5.webp\" alt=\"Strong Typing \u2014 Real C# Classes, Not Just JSON blobs\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/cd1\/c1f\/e3c\/cd1c1fe3cbc89378ed7f1539bf0941b5.webp 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/cd1\/c1f\/e3c\/cd1c1fe3cbc89378ed7f1539bf0941b5.webp 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Strong Typing \u2014 Real C# Classes, Not Just JSON blobs<\/figcaption><\/div>\n<\/figure>\n<h4>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430<\/h4>\n<p>\u0412\u043e\u0437\u044c\u043c\u0451\u043c \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u0439 enterprise-\u043e\u0431\u044a\u0435\u043a\u0442 \u2014 \u0441\u043a\u0430\u0436\u0435\u043c, \u0437\u0430\u043a\u0430\u0437. \u041e\u043d \u0441\u0432\u044f\u0437\u0430\u043d \u0441 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u043c, \u043f\u043e\u0437\u0438\u0446\u0438\u044f\u043c\u0438, \u043a\u0430\u0436\u0434\u0430\u044f \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u2014 \u0441 \u0442\u043e\u0432\u0430\u0440\u043e\u043c, \u0443 \u0442\u043e\u0432\u0430\u0440\u0430 \u2014 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f, \u0443 \u0437\u0430\u043a\u0430\u0437\u0430 \u2014 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u0441 \u0430\u0434\u0440\u0435\u0441\u043e\u043c, \u043e\u043f\u043b\u0430\u0442\u0430 \u0441 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u043c\u0438. \u0418\u0442\u043e\u0433\u043e 10\u201330 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439. EF Core:<\/p>\n<pre><code>var order = await context.Orders    .Include(o =&gt; o.Customer)    .Include(o =&gt; o.Items).ThenInclude(i =&gt; i.Product)        .ThenInclude(p =&gt; p.Category)    .Include(o =&gt; o.Items).ThenInclude(i =&gt; i.Discounts)    .Include(o =&gt; o.Shipping).ThenInclude(s =&gt; s.Address)    .Include(o =&gt; o.Payment).ThenInclude(p =&gt; p.Transactions)    \/\/ ... \u0435\u0449\u0451 \u0441\u0442\u0440\u043e\u043a 30 ...    .FirstOrDefaultAsync(o =&gt; o.Id == orderId);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0417\u0430\u0431\u044b\u043b \u043e\u0434\u0438\u043d Include \u2014 runtime. \u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u0435 \u0432 \u043c\u043e\u0434\u0435\u043b\u044c \u2014 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u2192 \u0437\u0430\u044f\u0432\u043a\u0430 \u043d\u0430 DBA \u2192 staging \u2192 deploy. \u0422\u0440\u0438 \u0434\u043d\u044f \u043d\u0430 <code>ALTER TABLE ADD COLUMN<\/code>.<\/p>\n<p>\u0425\u043e\u0442\u0435\u043b\u043e\u0441\u044c: \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043c\u043e\u0434\u0435\u043b\u044c \u043a\u0430\u043a C#-\u043a\u043b\u0430\u0441\u0441, \u0438 \u0447\u0442\u043e\u0431\u044b \u0434\u0432\u0438\u0436\u043e\u043a \u0441\u0430\u043c \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0441\u044f \u043a\u0430\u043a \u044d\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c. \u0411\u0435\u0437 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439, \u0431\u0435\u0437 \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0430, \u0431\u0435\u0437 Include.<\/p>\n<p>\u041d\u0430\u043f\u0438\u0441\u0430\u043b. \u0412\u044b\u043b\u043e\u0436\u0438\u043b \u043f\u043e\u0434 Apache 2.0.<\/p>\n<h4>Production case<\/h4>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043f\u0440\u043e\u0434\u0435 \u0432 \u043a\u0440\u0443\u043f\u043d\u043e\u043c HoReCa-\u0434\u0438\u0441\u0442\u0440\u0438\u0431\u044c\u044e\u0442\u043e\u0440\u0435 (~150k \u0437\u0430\u043a\u0430\u0437\u043e\u0432\/\u043c\u0435\u0441, ~20k B2B-\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432, \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043f\u0430\u0440\u043a). \u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044f\u044f TMS \u2014 ~500 \u0432\u043e\u0434\u0438\u0442\u0435\u043b\u0435\u0439 + ~50 \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u043e\u0432, 3-\u043d\u043e\u0434\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0442\u0435\u0440 (Xeon, 4 \u044f\u0434\u0440\u0430 \/ 8 \u0413\u0411 \/ 50 \u0413\u0411 SSD \u043d\u0430 \u043d\u043e\u0434\u0443), ~3 \u043c\u0435\u0441\u044f\u0446\u0430 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b, 10\u201315% CPU \u043f\u043e\u0434 \u043f\u043e\u043b\u043d\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439. \u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 redb.Route: SAP, Kafka, RabbitMQ, GPS-\u0444\u0438\u0434\u044b, \u041c\u0435\u0440\u043a\u0443\u0440\u0438\u0439, \u0415\u0413\u0410\u0418\u0421, \u0427\u0435\u0441\u0442\u043d\u044b\u0439 \u0417\u041d\u0410\u041a, \u0424\u0413\u0418\u0421 \u0417\u0435\u0440\u043d\u043e.<\/p>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 production-\u043f\u0440\u043e\u0434\u0443\u043a\u0442: \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 (~672k \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, ~8M \u0441\u0432\u043e\u0439\u0441\u0442\u0432). \u041d\u0438 \u043e\u0434\u043d\u043e\u0439 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0437\u0430 \u0432\u0435\u0441\u044c \u0441\u0440\u043e\u043a \u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0430\u0446\u0438\u0438. \u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u0435 \u0432 \u043c\u043e\u0434\u0435\u043b\u044c \u2014 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u0432 C#-\u043a\u043b\u0430\u0441\u0441 \u2192 <code>SyncSchemeAsync()<\/code> \u2192 \u0433\u043e\u0442\u043e\u0432\u043e.<\/p>\n<h4>\u041a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432 \u043a\u043e\u0434\u0435<\/h4>\n<p>\u0412\u043e\u0442 \u0440\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0438\u0437 <code>redb.Examples<\/code>:<\/p>\n<pre><code>[RedbScheme(\"Employee\")]public class EmployeeProps{    public string FirstName { get; set; } = \"\";    public string LastName { get; set; } = \"\";    public int Age { get; set; }    public decimal Salary { get; set; }    public string Department { get; set; } = \"\";    public DateTime HireDate { get; set; }    public string[]? Skills { get; set; }    public Address? HomeAddress { get; set; }    public Contact[]? Contacts { get; set; }    public RedbObject&lt;ProjectMetricsProps&gt;? CurrentProject { get; set; }    public RedbObject&lt;ProjectMetricsProps&gt;[]? PastProjects { get; set; }    public Dictionary&lt;int, decimal&gt;? BonusByYear { get; set; }    public Dictionary&lt;string, Department&gt;? DepartmentHistory { get; set; }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e <strong>\u0432\u0441\u044f \u0441\u0445\u0435\u043c\u0430<\/strong>. \u0412\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b, \u043c\u0430\u0441\u0441\u0438\u0432\u044b, \u0441\u043b\u043e\u0432\u0430\u0440\u0438, \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 RedbObject \u2014 \u0432\u0441\u0451 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.<\/p>\n<p><strong>\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c:<\/strong><\/p>\n<pre><code>var employee = new RedbObject&lt;EmployeeProps&gt;{    name = \"Alice Johnson\",    Props = new EmployeeProps    {        FirstName = \"Alice\",        LastName = \"Johnson\",        Age = 28,        Salary = 85000m,        Skills = [\"C#\", \"React\", \"SQL\"]    }};await redb.SaveAsync(employee);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c:<\/strong><\/p>\n<pre><code>var loaded = await redb.LoadAsync&lt;EmployeeProps&gt;(id);\/\/ loaded.Props.FirstName \u2192 \"Alice\"\/\/ loaded.Props.CurrentProject.Props \u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\/\/ loaded.Props.Contacts[0].Value \u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u0417\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c:<\/strong><\/p>\n<pre><code>var results = await redb.Query&lt;EmployeeProps&gt;()    .Where(e =&gt; e.Salary &gt; 75000m &amp;&amp; e.Skills.Contains(\"C#\"))    .OrderByDescending(e =&gt; e.Salary)    .Take(100)    .ToListAsync();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0417\u0430\u0431\u044b\u0442\u044c Include \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u2014 Props \u0432\u0441\u0435\u0433\u0434\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d \u0446\u0435\u043b\u0438\u043a\u043e\u043c. \u041d\u0443\u0436\u043d\u0430 \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f? \u0422\u043e\u0433\u0434\u0430 <code>.Select()<\/code>:<\/p>\n<pre><code>var projected = await redb.Query&lt;EmployeeProps&gt;()    .Select(x =&gt; new { x.Props.FirstName, x.Props.Salary })    .ToListAsync();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0421\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435: \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u0438\u0437 28 \u0442\u0430\u0431\u043b\u0438\u0446<\/h4>\n<p>\u0412 EF Core:<\/p>\n<pre><code>var order = await context.Orders    .Include(o =&gt; o.Customer)    .Include(o =&gt; o.Items).ThenInclude(i =&gt; i.Product)        .ThenInclude(p =&gt; p.Category)    .Include(o =&gt; o.Items).ThenInclude(i =&gt; i.Discounts)    .Include(o =&gt; o.Shipping).ThenInclude(s =&gt; s.Address)    .Include(o =&gt; o.Payment).ThenInclude(p =&gt; p.Transactions)    \/\/ ... \u0435\u0449\u0451 35 \u0441\u0442\u0440\u043e\u043a Include ...    .FirstOrDefaultAsync(o =&gt; o.Id == orderId);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 RedBase:<\/p>\n<pre><code>var order = await redb.LoadAsync&lt;OrderProps&gt;(orderId);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430. \u0412\u0441\u0435 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043c\u0430\u0441\u0441\u0438\u0432\u044b, \u0441\u043b\u043e\u0432\u0430\u0440\u0438 \u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u044b. \u0418 \u0431\u044b\u0441\u0442\u0440\u043e: \u0434\u0432\u0438\u0436\u043e\u043a \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 Props \u043e\u0434\u043d\u0438\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c \u043a \u043f\u043b\u043e\u0441\u043a\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435, \u0431\u0435\u0437 JOIN-\u043a\u0430\u0441\u043a\u0430\u0434\u0430 \u043f\u043e 28 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c. \u0412 Pro \u2014 \u0435\u0449\u0451 \u0431\u044b\u0441\u0442\u0440\u0435\u0435: \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0447\u0435\u0440\u0435\u0437 <code>Parallel.ForEach<\/code>, \u043a\u0430\u0436\u0434\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u0433\u0440\u0430\u0444\u0430 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e.<\/p>\n<h4>\u0410 \u0447\u0442\u043e \u0441 \u0434\u0435\u0440\u0435\u0432\u044c\u044f\u043c\u0438?<\/h4>\n<p>\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438. \u041d\u0435 \u043d\u0443\u0436\u0435\u043d \u043d\u0438 closure table \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u043d\u0438 \u0440\u0435\u043a\u0443\u0440\u0441\u0438\u0432\u043d\u044b\u0435 CTE \u0432 raw SQL:<\/p>\n<pre><code>\/\/ \u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0434\u0435\u0440\u0435\u0432\u043eawait redb.CreateChildAsync(department, parentDepartment);\/\/ \u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0432\u0441\u0451 \u0434\u0435\u0440\u0435\u0432\u043e (\u0434\u043e 5 \u0443\u0440\u043e\u0432\u043d\u0435\u0439)var tree = await redb.LoadTreeAsync&lt;DepartmentProps&gt;(rootId, maxDepth: 5);\/\/ LINQ \u043f\u043e \u0434\u0435\u0440\u0435\u0432\u0443var bigDepts = await redb.TreeQuery&lt;DepartmentProps&gt;()    .Where(d =&gt; d.Budget &gt; 500000m)    .WhereLevel(2)    .ToListAsync();\/\/ \u041d\u0430\u0439\u0442\u0438 \u0432\u0441\u0435\u0445, \u0443 \u043a\u043e\u0433\u043e \u043f\u0440\u0435\u0434\u043e\u043a \u0441 \u0431\u044e\u0434\u0436\u0435\u0442\u043e\u043c &gt; 1Mvar rich = await redb.TreeQuery&lt;DepartmentProps&gt;()    .WhereHasAncestor&lt;DepartmentProps&gt;(a =&gt; a.Budget &gt; 1_000_000m)    .ToListAsync();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u041e\u043a\u043e\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438? GroupBy? \u0410\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u0438?<\/h4>\n<p>\u0422\u043e\u0436\u0435 \u0447\u0435\u0440\u0435\u0437 LINQ:<\/p>\n<pre><code>\/\/ ROW_NUMBER() PARTITION BY Department ORDER BY Salary DESCvar ranked = await redb.Query&lt;EmployeeProps&gt;()    .WithWindow(w =&gt; w        .PartitionBy(x =&gt; x.Department)        .OrderByDesc(x =&gt; x.Salary))    .SelectAsync(x =&gt; new    {        Name = x.Props.FirstName,        Department = x.Props.Department,        Rank = Win.RowNumber()    });\/\/ GroupBy + \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u044fvar stats = await redb.Query&lt;EmployeeProps&gt;()    .GroupBy(x =&gt; x.Department)    .SelectAsync(g =&gt; new    {        g.Key,        Total = Agg.Count(),        AvgSalary = Agg.Average(g, x =&gt; x.Salary)    });<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>\u0427\u0442\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0434\u0432\u0438\u0436\u043e\u043a \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c<\/h4>\n<p>\u041e\u0431\u044b\u0447\u043d\u043e SQL \u0437\u0430 LINQ-\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 \u0441\u043a\u0443\u0447\u043d\u044b\u0439. \u041d\u043e \u0432\u043e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u0447\u0435\u043c\u0443 \u0437\u0434\u0435\u0441\u044c \u043d\u0443\u0436\u0435\u043d \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 query engine, \u0430 \u043d\u0435 \u00ab\u043f\u0440\u043e\u0441\u0442\u043e ORM\u00bb.<\/p>\n<p>\u041c\u043e\u0434\u0435\u043b\u044c:<\/p>\n<pre><code>public class Address{    public string City   { get; set; } = string.Empty;    public string Street { get; set; } = string.Empty;}[RedbScheme(\"Employee\")]public class EmployeeProps{    \/\/ ...    public Dictionary&lt;string, Address&gt;? OfficeLocations { get; set; }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>LINQ-\u0437\u0430\u043f\u0440\u043e\u0441 \u2014 \u043d\u0430\u0439\u0442\u0438 \u0432\u0441\u0435\u0445 \u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u043a\u043e\u0432, \u0443 \u043a\u043e\u0433\u043e HQ-\u043e\u0444\u0438\u0441 \u0432 \u041d\u044c\u044e-\u0419\u043e\u0440\u043a\u0435:<\/p>\n<pre><code>var result = await redb.Query&lt;EmployeeProps&gt;()    .Where(e =&gt; e.OfficeLocations![\"HQ\"].City == \"New York\")    .Take(100)    .ToListAsync();<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f <strong>PostgreSQL<\/strong>:<\/p>\n<pre><code class=\"sql\">-- PVT CTE (nested-only optimization): OfficeLocations[HQ].CityWITH pvt_cte AS (  WITH  nested_dict_0 AS (    SELECT dp._id_object         , (array_agg(nv._string)              FILTER (WHERE nv._id_structure = $5))[1] AS \"OfficeLocations[HQ].City\"    FROM _values dp    LEFT JOIN _values nv           ON nv._array_parent_id = dp._id          AND nv._id_structure = $5    WHERE dp._id_structure = $3       -- \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0441\u043b\u043e\u0432\u0430\u0440\u044f OfficeLocations      AND dp._array_index  = $4       -- \u043a\u043b\u044e\u0447 \"HQ\"      AND dp._id_object IN (            SELECT _id FROM _objects WHERE _id_scheme = $1          )    GROUP BY dp._id_object  )  SELECT nd0._id_object       , nd0.\"OfficeLocations[HQ].City\"  FROM nested_dict_0 nd0  WHERE nd0.\"OfficeLocations[HQ].City\" = $2   -- \"New York\")SELECT o.*FROM _objects oJOIN pvt_cte ON pvt_cte._id_object = o._idWHERE o._id_scheme = $1LIMIT 100<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0427\u0442\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f <strong>MSSQL<\/strong>:<\/p>\n<pre><code class=\"sql\">-- PVT CTE (nested MAX CASE WHEN): OfficeLocations[HQ].City;WITHraw_values AS (  SELECT nv._array_parent_id AS _parent_id       , MAX(CASE WHEN nv._id_structure = 1010067                  THEN nv._string END) AS [OfficeLocations$LHQ$R$DCity]  FROM _values nv  WHERE nv._id_structure IN (1010067)    AND nv._array_parent_id IS NOT NULL  GROUP BY nv._array_parent_id),pvt_cte AS (  SELECT dp._id_object       , rv.[OfficeLocations$LHQ$R$DCity]  FROM _values dp  JOIN raw_values rv ON rv._parent_id = dp._id  WHERE dp._id_structure = @p2      -- \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0441\u043b\u043e\u0432\u0430\u0440\u044f OfficeLocations    AND dp._array_index  = @p3      -- \u043a\u043b\u044e\u0447 \"HQ\"    AND dp._id_object IN (          SELECT _id FROM _objects WHERE _id_scheme = @p0        )    AND rv.[OfficeLocations$LHQ$R$DCity] = @p1   -- \"New York\")SELECT o.*FROM _objects oJOIN pvt_cte ON pvt_cte._id_object = o._idWHERE o._id_scheme = @p0ORDER BY o._idOFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 LINQ \u2014 \u0434\u0432\u0430 \u0434\u0438\u0430\u043b\u0435\u043a\u0442\u0430, \u0434\u0432\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u043a \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438 (PostgreSQL \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <code>array_agg FILTER<\/code>, MSSQL \u2014 <code>MAX CASE WHEN<\/code>). SQL \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0434 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u0434\u0438\u0430\u043b\u0435\u043a\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438. \u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0447\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u043e\u0441\u044c \u0432\u0441\u0435\u0433\u0434\u0430 \u043c\u043e\u0436\u043d\u043e \u0447\u0435\u0440\u0435\u0437 <code>.ToSqlStringAsync()<\/code>.<\/p>\n<h4>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u2014 5 \u0441\u0442\u0440\u043e\u043a<\/h4>\n<p>PostgreSQL \u0438\u043b\u0438 MSSQL \u2014 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439:<\/p>\n<pre><code>\/\/ PostgreSQL + Pro\/\/ jit=off \u2014 \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u043c JIT-\u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u044e PostgreSQL, \u043d\u0430 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u0445 \u043e\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043c\u0435\u0434\u043b\u044f\u0435\u0442builder.Services.AddRedbPro(options =&gt; options    .UsePostgres(\"Host=localhost;Database=mydb;Username=postgres;Password=pass;Options=-c jit=off\")    .WithLicense(\"YOUR-LICENSE-KEY\")    .Configure(c =&gt;    {        c.EnablePropsCache = true;        c.EnableLazyLoadingForProps = false;    }));\/\/ MSSQL + Probuilder.Services.AddRedbPro(options =&gt; options    .UseMsSql(\"Server=localhost;Database=mydb;Trusted_Connection=true\")    .WithLicense(\"YOUR-LICENSE-KEY\"));\/\/ Free (Apache 2.0) \u2014 PostgreSQLbuilder.Services.AddRedb(options =&gt; options    .UsePostgres(\"Host=localhost;Database=mydb;Username=postgres;Password=pass;Options=-c jit=off\"));\/\/ Free (Apache 2.0) \u2014 MSSQLbuilder.Services.AddRedb(options =&gt; options    .UseMsSql(\"Server=localhost;Database=mydb;Trusted_Connection=true\"));<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h4>Free vs Pro<\/h4>\n<p>\u042f\u0434\u0440\u043e \u2014 open source, Apache 2.0. PostgreSQL \u0438 MSSQL. \u041f\u043e\u043b\u043d\u044b\u0439 LINQ, \u0434\u0435\u0440\u0435\u0432\u044c\u044f, \u0441\u043f\u0438\u0441\u043a\u0438, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438, \u044d\u043a\u0441\u043f\u043e\u0440\u0442\/\u0438\u043c\u043f\u043e\u0440\u0442.<\/p>\n<p>\u041e\u0431\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0438 \u0441 PostgreSQL, \u0438 \u0441 MSSQL. \u0423 \u0434\u0432\u0438\u0436\u043a\u0430 \u0435\u0434\u0438\u043d\u044b\u0439 SQL-\u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u2014 \u043f\u0435\u0440\u0435\u0435\u0437\u0434 \u043c\u0435\u0436\u0434\u0443 \u0431\u0430\u0437\u0430\u043c\u0438 \u044d\u0442\u043e \u0441\u043c\u0435\u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 (<code>.UsePostgres()<\/code> \u2194 <code>.UseMsSql()<\/code>). \u0410 <code>redb.Export<\/code> \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u043e\u0434\u043d\u043e\u0439 \u0431\u0430\u0437\u044b \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u0434\u0440\u0443\u0433\u0443\u044e \u2014 PostgreSQL \u2192 MSSQL \u0438 \u043e\u0431\u0440\u0430\u0442\u043d\u043e.<\/p>\n<p>Pro \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>Compiled queries \u2014 LINQ \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 SQL, \u0431\u0435\u0437 JSON-\u0438\u043d\u0442\u0435\u0440\u043f\u0440\u0435\u0442\u0430\u0442\u043e\u0440\u0430<\/p>\n<\/li>\n<li>\n<p>Parallel materialization \u2014 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 Props \u0447\u0435\u0440\u0435\u0437 <code>Parallel.ForEach<\/code><\/p>\n<\/li>\n<li>\n<p>Change tracking \u2014 \u0443\u043c\u043d\u043e\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435: \u0441\u0442\u0440\u043e\u044f\u0442\u0441\u044f \u0434\u0432\u0430 \u0434\u0435\u0440\u0435\u0432\u0430 <code>ValueTreeNode<\/code> (\u043f\u0430\u043c\u044f\u0442\u044c vs \u0411\u0414), diff \u0441 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043e\u043c \u043f\u043e \u0445\u0435\u0448\u0443, \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u043c\u0435\u043d\u0451\u043d\u043d\u044b\u0435 \u0443\u0437\u043b\u044b \u2192 SQL. \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e delete-all\/re-insert<\/p>\n<\/li>\n<li>\n<p>Window functions, \u0433\u043b\u0443\u0431\u043e\u043a\u0438\u0435 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u0430\u0440\u0438\u0444\u043c\u0435\u0442\u0438\u043a\u0430 \u0432 WHERE<\/p>\n<\/li>\n<\/ul>\n<p>\u0411\u0435\u0437 \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0438 Pro \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u2014 1,024 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043d\u0430 \u0437\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0441\u0447\u0451\u0442\u0447\u0438\u043a \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0435. \u0414\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0435\u0442.<\/p>\n<h4>Raw SQL \u0438 \u0441\u0432\u043e\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u2014 \u0442\u043e\u0436\u0435 \u043c\u043e\u0436\u043d\u043e<\/h4>\n<p>RedBase \u043d\u0435 \u0437\u0430\u043a\u0440\u044b\u0442\u044b\u0439 \u044f\u0449\u0438\u043a. \u0415\u0441\u043b\u0438 \u043d\u0430\u0434\u043e \u2014 \u0440\u0430\u0431\u043e\u0442\u0430\u0439 \u0441 \u0411\u0414 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e.<\/p>\n<p><strong>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 SQL<\/strong> \u2014 \u0430\u043d\u0430\u043b\u043e\u0433 EF Core <code>.ToQueryString()<\/code>:<\/p>\n<pre><code>var sql = await redb.Query&lt;EmployeeProps&gt;()    .Where(e =&gt; e.Salary &gt; 75000m &amp;&amp; e.Department == \"Engineering\")    .ToSqlStringAsync();\/\/ \u0432\u0435\u0440\u043d\u0451\u0442 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 SQL \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u2014 \u0443\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0438 \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0443\u044e SQL-\u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>get_object_json<\/code> \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u2014 \u043e\u043d\u0430 \u0435\u0441\u0442\u044c \u0438 \u0432 PostgreSQL, \u0438 \u0432 MSSQL, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u043a\u0430\u043a JSON, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0432\u0441\u0435 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 Props \u0438 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u043d\u0430 \u0437\u0430\u0434\u0430\u043d\u043d\u0443\u044e \u0433\u043b\u0443\u0431\u0438\u043d\u0443:<\/p>\n<pre><code class=\"sql\">-- PostgreSQLSELECT get_object_json(42, 3);       -- \u043e\u0431\u044a\u0435\u043a\u0442 42, \u0433\u043b\u0443\u0431\u0438\u043d\u0430 3SELECT get_object_json(o._id, 5)FROM _objects oWHERE o._id_scheme = 123;-- MSSQLSELECT dbo.get_object_json(42, 3);SELECT dbo.get_object_json(o._id, 5)FROM _objects oWHERE o._id_scheme = 123;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u043b\u0435\u0437\u043d\u043e \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0438 \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0438 \u043f\u0440\u044f\u043c\u043e \u0432 psql\/DataGrip\/SSMS, \u0438\u043b\u0438 \u043a\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u0435\u043d JSON \u043d\u0430 SQL-\u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u2014 \u0431\u0435\u0437 C# \u043a\u043e\u0434\u0430.<\/p>\n<p><strong>\u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0439 SQL<\/strong> \u0447\u0435\u0440\u0435\u0437 <code>redb.Context.Db<\/code>:<\/p>\n<pre><code>\/\/ SELECT \u2014 \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432var rows = await redb.Context.Db.QueryAsync&lt;MyDto&gt;(    \"SELECT _id, _name FROM _objects WHERE _id_scheme = $1\", schemeId);\/\/ SELECT \u2014 \u0441\u043a\u0430\u043b\u044f\u0440var count = await redb.Context.Db.ExecuteScalarAsync&lt;int&gt;(    \"SELECT COUNT(*) FROM _objects WHERE _id_scheme = $1\", schemeId);\/\/ INSERT \/ UPDATE \/ DELETEawait redb.Context.Db.ExecuteAsync(    \"UPDATE my_custom_table SET synced = true WHERE object_id = $1\", objectId);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u0421\u0432\u043e\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b<\/strong> \u2014 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0439 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0442 \u0436\u0435 <code>ExecuteAsync<\/code>, \u0445\u043e\u0442\u044c \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code>await redb.Context.Db.ExecuteAsync(\"\"\"    CREATE TABLE IF NOT EXISTS logistics_routes (        id BIGSERIAL PRIMARY KEY,        object_id BIGINT REFERENCES _objects(_id) ON DELETE CASCADE,        route_json TEXT,        created_at TIMESTAMPTZ DEFAULT NOW()    )    \"\"\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>FK \u043d\u0430 <code>_objects(_id)<\/code> \u2014 \u0438 \u0442\u0432\u043e\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u0430 \u043a redb-\u043e\u0431\u044a\u0435\u043a\u0442\u0443. Cascade delete \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<p><strong>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 <\/strong><code><strong>_values<\/strong><\/code> \u2014 \u0435\u0441\u043b\u0438 \u043d\u0443\u0436\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c raw SQL \u043f\u043e \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0443 Props, \u043a\u043e\u043b\u043e\u043d\u043a\u0438 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u044b:<\/p>\n<pre><code class=\"sql\">-- \u041a\u0430\u0436\u0434\u043e\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u2014 \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 \u0432 _values-- _id_structure \u2192 \u043a\u0430\u043a\u043e\u0435 \u043f\u043e\u043b\u0435 \u0441\u0445\u0435\u043c\u044b (id \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0438\u0437 _structures)-- _id_object    \u2192 \u043a\u0430\u043a\u043e\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 (_objects._id)-- \u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u043a\u043e\u043b\u043e\u043d\u043a\u0435 \u043f\u043e \u0442\u0438\u043f\u0443 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430:--   _String          text--   _Long            bigint        (int, long, enum)--   _Numeric         numeric(38,18) (decimal \u2014 \u0431\u0435\u0437 \u043f\u043e\u0442\u0435\u0440\u044c \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438)--   _Double          float--   _DateTimeOffset  timestamptz--   _Boolean         boolean--   _Guid            uuid--   _Object          bigint \u2192 FK \u043d\u0430 _objects (RedbObject&lt;T&gt; reference)--   _ListItem        bigint \u2192 FK \u043d\u0430 _list_items (\u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a)--   _ByteArray       bytea-- \u0414\u043b\u044f \u043c\u0430\u0441\u0441\u0438\u0432\u043e\u0432\/\u0441\u043b\u043e\u0432\u0430\u0440\u0435\u0439: _array_parent_id + _array_index<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043d\u0435 <code>varchar(max)<\/code> \u2014 \u043d\u0435 \u00ab\u0432\u0441\u0451 \u0432 \u0441\u0442\u0440\u043e\u043a\u0443\u00bb. \u041a\u0430\u0436\u0434\u044b\u0439 \u0442\u0438\u043f \u0432 \u0441\u0432\u043e\u0435\u0439 \u043a\u043e\u043b\u043e\u043d\u043a\u0435 \u0441 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c SQL-\u0442\u0438\u043f\u043e\u043c. <code>decimal<\/code> \u2014 \u044d\u0442\u043e <code>NUMERIC(38,18)<\/code> \u0431\u0435\u0437 \u043f\u043e\u0442\u0435\u0440\u044c \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438. <code>DateTime<\/code> \u2014 <code>timestamptz<\/code>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 WHERE \u043f\u043e \u0447\u0438\u0441\u043b\u0430\u043c, \u0434\u0430\u0442\u0430\u043c, uuid \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0447\u0435\u0440\u0435\u0437 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u044b.<\/p>\n<h4>\u041a\u043e\u0433\u0434\u0430 RedBase \u041d\u0415 \u043d\u0443\u0436\u0435\u043d<\/h4>\n<p>RedBase \u0445\u0440\u0430\u043d\u0438\u0442 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0432 \u0441\u0442\u0440\u043e\u043a\u0430\u0445 \u0442\u0430\u0431\u043b\u0438\u0446\u044b <code>_values<\/code> \u2014 \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e. \u042d\u0442\u043e \u0434\u0430\u0451\u0442 \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c: \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043c\u0430\u0441\u0441\u0438\u0432\u044b, \u0441\u043b\u043e\u0432\u0430\u0440\u0438, \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0441\u0445\u0435\u043c\u044b \u0431\u0435\u0437 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439. \u041d\u043e \u0437\u0430 \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c \u0435\u0441\u0442\u044c \u0446\u0435\u043d\u0430.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u0448\u0430 \u043c\u043e\u0434\u0435\u043b\u044c \u2014 \u043f\u043b\u043e\u0441\u043a\u0430\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0431\u0435\u0437 \u0441\u0432\u044f\u0437\u0435\u0439 (<code>Users<\/code> \u0441 10 \u043a\u043e\u043b\u043e\u043d\u043a\u0430\u043c\u0438, \u043b\u043e\u0433 \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0437\u0430\u0434\u0430\u0447), \u0442\u043e:<\/p>\n<ul>\n<li>\n<p>Dapper + <code>SELECT * FROM users WHERE id = @id<\/code> \u0431\u0443\u0434\u0435\u0442 \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u2014 \u043e\u0434\u0438\u043d \u0437\u0430\u043f\u0440\u043e\u0441, \u043e\u0434\u0438\u043d \u043c\u0430\u043f\u043f\u0438\u043d\u0433, \u0431\u0435\u0437 overhead<\/p>\n<\/li>\n<li>\n<p>EF Core \u0442\u043e\u0436\u0435 \u0441\u043f\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u2014 \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u044b Include \u043d\u0435 \u043d\u0443\u0436\u043d\u044b, \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u044b<\/p>\n<\/li>\n<\/ul>\n<p>RedBase \u0432\u044b\u0438\u0433\u0440\u044b\u0432\u0430\u0435\u0442 \u0442\u0430\u043c, \u0433\u0434\u0435 \u043e\u0431\u044a\u0435\u043a\u0442 \u2014 \u044d\u0442\u043e \u0433\u0440\u0430\u0444: 3+ \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440, \u043c\u0430\u0441\u0441\u0438\u0432\u044b, \u0441\u043b\u043e\u0432\u0430\u0440\u0438, \u0441\u0441\u044b\u043b\u043a\u0438 \u043c\u0435\u0436\u0434\u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c\u0438, \u0447\u0430\u0441\u0442\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u0445\u0435\u043c\u044b. \u0427\u0435\u043c \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u043c\u043e\u0434\u0435\u043b\u044c \u2014 \u0442\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0440\u0430\u0437\u043d\u0438\u0446\u0430 \u0441 \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u043c.<\/p>\n<p>\u041f\u0440\u0438\u0447\u0451\u043c \u043f\u043e\u0440\u043e\u0433 \u043d\u0438\u0436\u0435, \u0447\u0435\u043c \u043a\u0430\u0436\u0435\u0442\u0441\u044f. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0432 EF Core \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u043c\u043e\u0434\u0435\u043b\u044c <code>string[] Skills<\/code> \u2014 \u0438 \u0432\u043e\u0442 \u0432\u0430\u043c \u0443\u0436\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 <code>UserSkills<\/code>, FK, \u0438\u043d\u0434\u0435\u043a\u0441\u044b, \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f, <code>.Include(u =&gt; u.Skills)<\/code>. \u0410 \u0432 RedBase \u2014 \u043f\u0440\u043e\u0441\u0442\u043e <code>public string[]? Skills { get; set; }<\/code> \u0438 \u0432\u0441\u0451. \u041e\u0431\u044a\u044f\u0432\u0438\u043b \u2014 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<h4>\u0417\u0430 \u0440\u0430\u043c\u043a\u0430\u043c\u0438 \u0441\u0442\u0430\u0442\u044c\u0438<\/h4>\n<p>\u0412 \u043e\u0434\u043d\u0443 \u0441\u0442\u0430\u0442\u044c\u044e \u043d\u0435 \u0432\u043b\u0435\u0437\u043b\u043e. \u041a\u0440\u0430\u0442\u043a\u043e \u0447\u0442\u043e \u0435\u0449\u0451 \u0435\u0441\u0442\u044c:<\/p>\n<ul>\n<li>\n<p><strong>\u0414\u0435\u0440\u0435\u0432\u044c\u044f<\/strong> \u2014 \u043f\u043e\u043b\u043d\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b: <code>CreateChildAsync<\/code>, <code>MoveAsync<\/code>, <code>LoadTreeAsync<\/code>, <code>WhereLevel<\/code>, <code>WhereHasAncestor<\/code>, <code>WhereHasDescendant<\/code>. Closure table \u0438 \u0440\u0435\u043a\u0443\u0440\u0441\u0438\u0432\u043d\u044b\u0435 CTE \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>\u042d\u043a\u0441\u043f\u043e\u0440\u0442 \/ \u0438\u043c\u043f\u043e\u0440\u0442<\/strong> \u2014 <code>redb.Export<\/code> \u0432\u044b\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0432 JSON-\u0444\u0430\u0439\u043b\u044b \u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043e\u0431\u0440\u0430\u0442\u043d\u043e. \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043c\u0435\u0436\u0434\u0443 PostgreSQL \u0438 MSSQL: \u043f\u0435\u0440\u0435\u0435\u0445\u0430\u0442\u044c \u0441 \u043e\u0434\u043d\u043e\u0439 \u0411\u0414 \u043d\u0430 \u0434\u0440\u0443\u0433\u0443\u044e \u2014 \u043e\u0434\u043d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430.<\/p>\n<\/li>\n<li>\n<p><strong>\u041f\u0440\u0430\u0432\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430<\/strong> \u2014 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u0440\u043e\u043b\u0435\u0439 \u0438 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432.<\/p>\n<\/li>\n<li>\n<p><strong>\u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438<\/strong> \u2014 <code>_list_items<\/code>, \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0447\u0435\u0440\u0435\u0437 <code>RedbListItem<\/code>, LINQ \u043f\u043e \u0441\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c.<\/p>\n<\/li>\n<li>\n<p><strong>Change tracking<\/strong> (Pro) \u2014 <code>PropsSaveStrategy.ChangeTracking<\/code>: \u043f\u0440\u0438 <code>SaveAsync<\/code> \u0441\u0442\u0440\u043e\u044f\u0442\u0441\u044f \u0434\u0432\u0430 \u0434\u0435\u0440\u0435\u0432\u0430 <code>ValueTreeNode<\/code> (\u043f\u0430\u043c\u044f\u0442\u044c vs \u0411\u0414), \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0441 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u043e\u043c \u043f\u043e \u0445\u0435\u0448\u0443 \u2014 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 SQL-\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439. \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e delete-all\/re-insert.<\/p>\n<\/li>\n<li>\n<p><strong>redb.Identity<\/strong> (\u0432 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435, \u0435\u0449\u0451 \u043d\u0435 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d \u043d\u0430 NuGet) \u2014 OAuth 2.1 \/ OIDC Identity Server \u043f\u043e\u0432\u0435\u0440\u0445 redb.Core \u0438 redb.Route. \u041a\u043b\u044e\u0447\u0435\u0432\u0430\u044f \u0438\u0434\u0435\u044f: \u043a\u0430\u0436\u0434\u044b\u0439 endpoint \u2014 \u044d\u0442\u043e <code>direct-vm:\/\/<\/code>-\u043c\u0430\u0440\u0448\u0440\u0443\u0442, \u0430 \u043d\u0435 HTTP-middleware. \u0412\u044b\u0437\u0432\u0430\u0442\u044c <code>token<\/code> \u0438\u0437 Worker Service \u0438\u043b\u0438 \u0438\u0437 \u0441\u043e\u0441\u0435\u0434\u043d\u0435\u0433\u043e \u043c\u043e\u0434\u0443\u043b\u044f \u0432 \u0442\u043e\u043c \u0436\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u2014 <code>To(\"direct-vm:\/\/identity-token\")<\/code>, \u0431\u0435\u0437 loopback, \u0431\u0435\u0437 TLS, \u0431\u0435\u0437 <code>WebApplicationFactory<\/code> \u0432 \u0442\u0435\u0441\u0442\u0430\u0445. HTTP \/ gRPC \/ RabbitMQ \u2014 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u044b\u0435 facade-\u043f\u0430\u043a\u0435\u0442\u044b. \u0418\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438: \u0432\u0441\u0435 \u0444\u043b\u043e\u0443\u0437\u044b OAuth 2.1 (Code+PKCE, Client Credentials, Device Code), PAR (RFC 9126), DPoP (RFC 9449), Dynamic Client Registration (RFC 7591\/7592), SCIM 2.0 (RFC 7643\/7644), FIDO2\/WebAuthn + TOTP + SMS OTP, backchannel logout (RFC 8417), \u0444\u0435\u0434\u0435\u0440\u0430\u0446\u0438\u044f (OIDC \/ GitHub). \u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 redb \u2014 \u0431\u0435\u0437 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439. \u0428\u0430\u0440\u0438\u0442 signing keys \u0438 DataProtection key-ring \u043c\u0435\u0436\u0434\u0443 \u043d\u043e\u0434\u0430\u043c\u0438 \u0447\u0435\u0440\u0435\u0437 redb object store. \u0414\u0435\u043f\u043b\u043e\u0438\u0442\u0441\u044f \u043a\u0430\u043a <code>.tpkg<\/code> \u0432 redb.Tsak. 1751 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0442\u0435\u0441\u0442. Apache 2.0.<\/p>\n<\/li>\n<li>\n<p><strong>195+ \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432<\/strong> \u0432 <code>redb.Examples<\/code> \u2014 \u0434\u0435\u0440\u0435\u0432\u044c\u044f, \u043e\u043a\u043d\u0430, \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438, \u044d\u043a\u0441\u043f\u043e\u0440\u0442, raw SQL \u0438 \u0442.\u0434.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u0441\u0451 \u044d\u0442\u043e \u0432 <a href=\"https:\/\/redbase.app\/architecture\" rel=\"noopener noreferrer nofollow\">\u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438<\/a> \u0438 <a href=\"https:\/\/redbase.app\/examples\" rel=\"noopener noreferrer nofollow\">\u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445<\/a><\/p>\n<h4>redb.Route: \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0431\u0435\u0437 hand-rolled-\u043a\u043e\u0434\u0430<\/h4>\n<p>redb.Core \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435. \u0414\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0439 \u0435\u0441\u0442\u044c <strong>redb.Route<\/strong> \u2014 .NET-\u0430\u043d\u0430\u043b\u043e\u0433 Apache Camel. \u041c\u0430\u0440\u0448\u0440\u0443\u0442 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442\u0441\u044f fluent C# DSL:<\/p>\n<pre><code>\/\/ HTTP-\u0432\u0445\u043e\u0434: \u043f\u0440\u0438\u043d\u044f\u0442\u044c \u0437\u0430\u043a\u0430\u0437, \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044cFrom(\"http:0.0.0.0:5090\/api\/orders?inOut=true&amp;cors=true&amp;corsOrigins=*\")    .Validate(e =&gt; e.In.Body is not null, \"Body required\")    .Choice()        .When(e =&gt; e.In.Headers.ContainsKey(\"redbHttp.ResponseCode\"))            .To(\"direct:\/\/error-response\")        .Otherwise()            .To(\"seda:\/\/orders-pending?concurrentConsumers=4\")    .EndChoice();\/\/ \u0424\u043e\u043d\u043e\u0432\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430: \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 redb, \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u0435From(\"seda:\/\/orders-pending?concurrentConsumers=4\")    .ProcessWithRedb(async (redb, exchange, ct) =&gt;    {        var dto = (OrderDto)exchange.In.Body!;        var order = new RedbObject&lt;OrderProps&gt; { Props = Map(dto) };        await redb.SaveAsync(order, ct);        exchange.In.Headers[\"order.id\"] = order.id;    })    .To(\"rabbitmq:\/\/orders-created\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>22 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u0430 + 5 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432:<\/strong><\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u044b<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u041e\u0447\u0435\u0440\u0435\u0434\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/p>\n<\/td>\n<td>\n<p align=\"left\">RabbitMQ, Kafka, IBM MQ, MQTT, Azure Service Bus<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">HTTP \/ WebSocket<\/p>\n<\/td>\n<td>\n<p align=\"left\">HTTP (in\/out), WebSocket, gRPC (client)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0424\u0430\u0439\u043b\u044b \/ \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\">SFTP, S3, FTP, File<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0411\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/td>\n<td>\n<p align=\"left\">SQL (polling outbox)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435<\/p>\n<\/td>\n<td>\n<p align=\"left\">Direct, SEDA, Timer, Cron, Mock<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p><strong>30+ EIP-\u043f\u0430\u0442\u0442\u0435\u0440\u043d\u043e\u0432<\/strong> \u2014 Split, Aggregate, Choice, Filter, WireTap, Retry, DeadLetterChannel, CircuitBreaker, IdempotentConsumer, Saga, Multicast, RecipientList, DynamicRouter, Resequence, Throttle, Delay, Loop, Enrich, Validate, Transacted, \u0438 \u0434\u0440\u0443\u0433\u0438\u0435.<\/p>\n<p><strong>Expression DSL<\/strong> \u2014 \u043f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432 <code>Func&lt;IExchange, T&gt;<\/code> \u0447\u0435\u0440\u0435\u0437 <code>System.Linq.Expressions<\/code>, \u0431\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u043f\u0440\u0435\u0442\u0430\u0442\u043e\u0440\u0430:<\/p>\n<pre><code>\/\/ \u041f\u0440\u0435\u0434\u0438\u043a\u0430\u0442\u044b \u0432 Choice.When, Filter, Retry \u2014 \u0432\u0441\u0435 \u0447\u0435\u0440\u0435\u0437 \u043e\u0434\u0438\u043d DSL.When(Header(\"priority\").isEqualTo(\"high\")).Filter(Header(\"score\").isGreaterThan(50)).Filter(Header(\"tag\").regex(@\"^urgent-.*-x\\d+$\")).When(Header(\"active\").and(Header(\"role\").isEqualTo(\"admin\")))\/\/ String templates \u2192 \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432 \u043b\u044f\u043c\u0431\u0434\u044b.SetHeader(\"reply\", \"${header.orderId}-confirmed\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a:<\/strong><\/p>\n<pre><code>From(\"kafka:\/\/payments?groupId=billing\")    .OnException&lt;TimeoutException&gt;()        .Retry(3, TimeSpan.FromSeconds(2))    .OnException&lt;ValidationException&gt;()        .To(\"direct:\/\/dlq\")    .End()    .Retry(5)        .BackOff(TimeSpan.FromSeconds(1), multiplier: 2)    .Process(async (e, ct) =&gt; { \/* \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 *\/ })    .To(\"rabbitmq:\/\/billing-confirmed\");<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b<\/strong> \u2014 <code>.Transacted()<\/code> \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 pipeline \u0432 <code>TransactionScope<\/code>, SQL-\u0442\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442 \u0431\u0438\u043d\u0434\u0438\u0442 ADO.NET-\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e \u043a \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0448\u0430\u0433\u0443.<\/p>\n<p>Apache 2.0, NuGet: <code>dotnet add package redb.Route<\/code>.<\/p>\n<h4>redb.Tsak: runtime-\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432<\/h4>\n<p>redb.Route \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 <em>\u0447\u0442\u043e<\/em> \u0434\u0435\u043b\u0430\u0435\u0442 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d. <strong>redb.Tsak<\/strong> \u2014 \u044d\u0442\u043e <em>\u0433\u0434\u0435<\/em>, <em>\u043a\u043e\u0433\u0434\u0430<\/em> \u0438 <em>\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043f\u0438\u0439<\/em> \u0435\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c.<\/p>\n<p>\u041a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430: \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d\u043e\u0432 \u0436\u0438\u0432\u0443\u0442 \u0432 \u043e\u0434\u043d\u043e\u043c <code>Program.cs<\/code>. \u0414\u043e\u0431\u0430\u0432\u0438\u043b \u043d\u043e\u0432\u044b\u0439 \u2014 \u043f\u0435\u0440\u0435\u0441\u043e\u0431\u0440\u0430\u043b \u0438 \u043f\u0435\u0440\u0435\u0434\u0435\u043f\u043b\u043e\u0438\u043b \u0432\u0441\u0451. \u041d\u0443\u0436\u043d\u043e \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043e\u0434\u0438\u043d \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u2014 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0439 \u0432\u0435\u0441\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441.<\/p>\n<p>Tsak \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u043e: \u043a\u0430\u0436\u0434\u044b\u0439 <code>RouteBuilder<\/code> \u0443\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u043c\u043e\u0434\u0443\u043b\u044c (<code>.dll<\/code> \u0438\u043b\u0438 <code>.tpkg<\/code>-\u0431\u0430\u043d\u0434\u043b), Tsak \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0435\u0433\u043e \u0432 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <code>AssemblyLoadContext<\/code> \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u043c \u0446\u0438\u043a\u043b\u043e\u043c \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u043e\u0442 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445.<\/p>\n<p><strong>\u0414\u0435\u043f\u043b\u043e\u0439 \u043d\u043e\u0432\u043e\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430:<\/strong><\/p>\n<pre><code class=\"bash\"># \u0421\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c DLL \u2014 Tsak \u043f\u043e\u0434\u0445\u0432\u0430\u0442\u0438\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438cp Orders.dll \/tsak\/Libs\/# \u0418\u043b\u0438 \u0447\u0435\u0440\u0435\u0437 CLI:tsak module upload orders --file Orders.tpkgtsak context start orders# \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043e\u0434\u0438\u043d \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0431\u0435\u0437 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430:tsak route stop orders order-pipeline# \u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0447\u0442\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442:tsak context listtsak route list orders<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><strong>\u0422\u0440\u0438 \u0440\u0435\u0436\u0438\u043c\u0430 \u0434\u0435\u043f\u043b\u043e\u044f:<\/strong><\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\u0420\u0435\u0436\u0438\u043c<\/p>\n<\/th>\n<th>\n<p align=\"left\">\u041a\u043e\u0433\u0434\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Standalone<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430, \u0442\u0435\u0441\u0442\u044b \u2014 in-memory, \u0431\u0435\u0437 \u0411\u0414<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Single-node + redb<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u0440\u043e\u0434\u0430\u043a\u0448\u043d \u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u043c\u0430\u0448\u0438\u043d\u0435 \u0441 \u043f\u0435\u0440\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u044b\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><code>Cluster<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0434 \u2014 leader election + \u0430\u0432\u0442\u043e\u043f\u0435\u0440\u0435\u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u0432<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p><strong>\u041a\u043b\u0430\u0441\u0442\u0435\u0440 \u0431\u0435\u0437 Redis \u0438 etcd.<\/strong> \u0412 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043d\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 Tsak \u043d\u0435 \u0442\u044f\u043d\u0435\u0442 \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440 \u2014 \u0432\u0441\u0451 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u0442\u043e\u0439 \u0436\u0435 redb-\u0431\u0430\u0437\u0435, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. Leader election, \u0441\u043f\u0438\u0441\u043e\u043a \u043d\u043e\u0434, \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043e\u0432 \u043f\u043e \u043d\u043e\u0434\u0430\u043c, DataProtection key-ring, JWKS signing keys \u2014 \u0432\u0441\u0451 \u044d\u0442\u043e redb-\u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0432 <code>_objects<\/code>\/<code>_values<\/code>. \u041c\u0430\u0440\u0448\u0440\u0443\u0442\u044b, \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0435 \u043a\u0430\u043a \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043d\u044b\u0435 (<code>cluster=true<\/code> \u0432 URI), \u0438\u0434\u0443\u0442 \u0447\u0435\u0440\u0435\u0437 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440: \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430, partitioning, \u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u043a\u0430 \u043c\u0435\u0436\u0434\u0443 \u043d\u043e\u0434\u0430\u043c\u0438 \u2014 \u0447\u0435\u0440\u0435\u0437 redb. Quartz \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0432\u043e\u0438 <code>AdoJobStore<\/code>-\u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0432 \u0442\u043e\u0439 \u0436\u0435 \u0411\u0414, \u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u0438\u0445 \u0441\u0430\u043c. \u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0434\u0443 \u0432 \u043a\u043b\u0430\u0441\u0442\u0435\u0440 = \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 Tsak \u0441 \u0442\u043e\u0439 \u0436\u0435 \u0441\u0442\u0440\u043e\u043a\u043e\u0439 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0411\u0414. \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e ZooKeeper, Consul \u0438\u043b\u0438 Redis.<\/p>\n<p><strong>\u0427\u0442\u043e \u0435\u0441\u0442\u044c \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438:<\/strong><\/p>\n<ul>\n<li>\n<p><strong>REST API<\/strong> \u2014 32 endpoint\u2019\u0430: \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430\u043c\u0438, \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u043c\u0438, \u043c\u043e\u0434\u0443\u043b\u044f\u043c\u0438, \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043e\u043c, scheduler\u2019\u043e\u043c, \u043b\u043e\u0433\u0430\u043c\u0438, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438<\/p>\n<\/li>\n<li>\n<p><strong>CLI<\/strong> \u2014 30 \u043a\u043e\u043c\u0430\u043d\u0434 \u0441 \u043f\u0440\u043e\u0444\u0438\u043b\u044f\u043c\u0438 \u0438 JSON-\u0432\u044b\u0432\u043e\u0434\u043e\u043c (\u0443\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f CI\/CD)<\/p>\n<\/li>\n<li>\n<p><strong>Blazor Server dashboard<\/strong> \u2014 10 \u0441\u0442\u0440\u0430\u043d\u0438\u0446: \u043c\u0435\u0442\u0440\u0438\u043a\u0438 CPU\/RAM\/GC, per-route latency, ring-buffer \u043b\u043e\u0433\u0438, watchdog-\u0441\u0442\u0430\u0442\u0443\u0441<\/p>\n<\/li>\n<li>\n<p><strong>Watchdog<\/strong> \u2014 \u0434\u0435\u0442\u0435\u043a\u0442\u0438\u0440\u0443\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0448\u0438\u0435 \u0438\u043b\u0438 \u0443\u043f\u0430\u0432\u0448\u0438\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b, \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442<\/p>\n<\/li>\n<li>\n<p><strong>Quartz scheduler<\/strong> \u2014 \u0438\u043d\u0436\u0435\u043a\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u043a\u0430\u0436\u0434\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442, <code>RAMJobStore<\/code> \u0434\u043b\u044f standalone, <code>AdoJobStore<\/code> \u0434\u043b\u044f \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430 \u2014 \u0441\u0445\u0435\u043c\u0430 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438<\/p>\n<\/li>\n<li>\n<p><strong>OpenTelemetry<\/strong> \u2014 Activities \u0438 Meters \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0438 \u0448\u0430\u0433, Prometheus scrape<\/p>\n<\/li>\n<li>\n<p><strong>API Key + HMAC-SHA256<\/strong> \u2014 \u0440\u043e\u043b\u0438, expiry, revocation, constant-time comparison<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u041a\u043e\u0434 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f Tsak \u2014 \u043e\u0434\u0438\u043d \u043c\u0435\u0442\u043e\u0434 \u0432 <\/strong><code><strong>InitRoute.cs<\/strong><\/code><strong>:<\/strong><\/p>\n<pre><code>\/\/ \u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 Tsak-\u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0441 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u043c\u0438public static class InitRoute{    public static IRouteContext main(IRouteContext context)    {        \/\/ \u041e\u0431\u044b\u0447\u043d\u044b\u0439 redb.Route \u2014 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043e\u0434, \u0447\u0442\u043e \u0438 \u0431\u0435\u0437 Tsak        ((RouteContext)context).AddRoutes(new OrderRoutes());        ((RouteContext)context).AddRoutes(new ShipmentRoutes());        return context;    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>RouteBuilder<\/code>, \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u0434\u043b\u044f \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e <code>IHostedService<\/code>, \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 Tsak \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u2014 \u0442\u043e\u0442 \u0436\u0435 <code>Configure()<\/code>, \u0442\u043e\u0442 \u0436\u0435 <code>IExchange<\/code>, \u0442\u0435 \u0436\u0435 <code>OnException<\/code> \u0438 <code>.Transacted()<\/code>.<\/p>\n<p>351 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u0442\u0435\u0441\u0442. Apache 2.0.<\/p>\n<hr\/>\n<h4>\u0418\u0442\u043e\u0433\u043e<\/h4>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">\n<\/th>\n<th>\n<p align=\"left\">EF Core<\/p>\n<\/th>\n<th>\n<p align=\"left\">redb<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u0411\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041c\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u043e\u0432<\/p>\n<\/td>\n<td>\n<p align=\"left\">PostgreSQL + MSSQL<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u0421\u0445\u0435\u043c\u0430<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">DbContext + Fluent API + \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">C#-\u043a\u043b\u0430\u0441\u0441 + <code>[RedbScheme]<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0433\u0440\u0430\u0444 \u0438\u0437 28 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">40 Include, 200 \u0441\u0442\u0440\u043e\u043a<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>LoadAsync&lt;T&gt;(id)<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u0435<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u2192 DBA \u2192 staging \u2192 deploy<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u2192 <code>SyncSchemeAsync()<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u0414\u0435\u0440\u0435\u0432\u044c\u044f<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">closure table \u0432\u0440\u0443\u0447\u043d\u0443\u044e<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>TreeQuery&lt;T&gt;()<\/code>, CTE, \u0443\u0440\u043e\u0432\u043d\u0438, \u043f\u0440\u0435\u0434\u043a\u0438<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u041e\u043a\u043e\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">Raw SQL<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>WithWindow()<\/code>, <code>Win.RowNumber()<\/code><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u0417\u0430\u0431\u044b\u043b Include<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">Runtime crash<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u041f\u0435\u0440\u0435\u0435\u0437\u0434 \u043c\u0435\u0436\u0434\u0443 \u0411\u0414<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0420\u0443\u0447\u043d\u0430\u044f \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u0430<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>.UsePostgres()<\/code> \u2194 <code>.UseMsSql()<\/code> + Export<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p><a href=\"https:\/\/github.com\/redbase-app\" rel=\"noopener noreferrer nofollow\">GitHub org (\u0432\u0441\u0435 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438)<\/a><br \/> <a href=\"https:\/\/github.com\/redbase-app\/redb\" rel=\"noopener noreferrer nofollow\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 redb.Core<\/a><br \/> <a href=\"https:\/\/redbase.app\" rel=\"noopener noreferrer nofollow\">\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u044b (EN)<\/a><br \/> <a href=\"https:\/\/redb.ru\" rel=\"noopener noreferrer nofollow\">\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u044b (RU)<\/a><br \/> <a href=\"https:\/\/www.nuget.org\/packages?q=redb.\" rel=\"noopener noreferrer nofollow\">43 NuGet-\u043f\u0430\u043a\u0435\u0442\u0430<\/a><br \/> <a href=\"https:\/\/redbase.app\/architecture\" rel=\"noopener noreferrer nofollow\">\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 (\u0438\u043d\u0434\u0435\u043a\u0441\u044b, query engine)<\/a><br \/> <a href=\"https:\/\/redbase.app\/examples\" rel=\"noopener noreferrer nofollow\">195+ \u0440\u0430\u0431\u043e\u0447\u0438\u0445 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432<\/a><\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1042058\/\">https:\/\/habr.com\/ru\/articles\/1042058\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Strong Typing \u2014 Real C# Classes, Not Just JSON blobs\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u0412\u043e\u0437\u044c\u043c\u0451\u043c \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u0439 enterprise-\u043e\u0431\u044a\u0435\u043a\u0442 \u2014 \u0441\u043a\u0430\u0436\u0435\u043c, \u0437\u0430\u043a\u0430\u0437. \u041e\u043d \u0441\u0432\u044f\u0437\u0430\u043d \u0441 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u043c, \u043f\u043e\u0437\u0438\u0446\u0438\u044f\u043c\u0438, \u043a\u0430\u0436\u0434\u0430\u044f \u043f\u043e\u0437\u0438\u0446\u0438\u044f \u2014 \u0441 \u0442\u043e\u0432\u0430\u0440\u043e\u043c, \u0443 \u0442\u043e\u0432\u0430\u0440\u0430 \u2014 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f, \u0443 \u0437\u0430\u043a\u0430\u0437\u0430 \u2014 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u0441 \u0430\u0434\u0440\u0435\u0441\u043e\u043c, \u043e\u043f\u043b\u0430\u0442\u0430 \u0441 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u043c\u0438. \u0418\u0442\u043e\u0433\u043e 10\u201330 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439. EF Core:var order = await context.Orders    .Include(o =&gt; o.Customer)    .Include(o =&gt; o.Items).ThenInclude(i =&gt; i.Product)        .ThenInclude(p =&gt; p.Category)    .Include(o =&gt; o.Items).ThenInclude(i =&gt; i.Discounts)    .Include(o =&gt; o.Shipping).ThenInclude(s =&gt; s.Address)    .Include(o =&gt; o.Payment).ThenInclude(p =&gt; p.Transactions)    \/\/ &#8230; \u0435\u0449\u0451 \u0441\u0442\u0440\u043e\u043a 30 &#8230;    .FirstOrDefaultAsync(o =&gt; o.Id == orderId);\u0417\u0430\u0431\u044b\u043b \u043e\u0434\u0438\u043d Include \u2014 runtime. \u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u0435 \u0432 \u043c\u043e\u0434\u0435\u043b\u044c \u2014 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u2192 \u0437\u0430\u044f\u0432\u043a\u0430 \u043d\u0430 DBA \u2192 staging \u2192 deploy. \u0422\u0440\u0438 \u0434\u043d\u044f \u043d\u0430 ALTER TABLE ADD COLUMN.\u0425\u043e\u0442\u0435\u043b\u043e\u0441\u044c: \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u043c\u043e\u0434\u0435\u043b\u044c \u043a\u0430\u043a C#-\u043a\u043b\u0430\u0441\u0441, \u0438 \u0447\u0442\u043e\u0431\u044b \u0434\u0432\u0438\u0436\u043e\u043a \u0441\u0430\u043c \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0441\u044f \u043a\u0430\u043a \u044d\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c. \u0411\u0435\u0437 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439, \u0431\u0435\u0437 \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0430, \u0431\u0435\u0437 Include.\u041d\u0430\u043f\u0438\u0441\u0430\u043b. \u0412\u044b\u043b\u043e\u0436\u0438\u043b \u043f\u043e\u0434 Apache 2.0.Production case\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043f\u0440\u043e\u0434\u0435 \u0432 \u043a\u0440\u0443\u043f\u043d\u043e\u043c HoReCa-\u0434\u0438\u0441\u0442\u0440\u0438\u0431\u044c\u044e\u0442\u043e\u0440\u0435 (~150k \u0437\u0430\u043a\u0430\u0437\u043e\u0432\/\u043c\u0435\u0441, ~20k B2B-\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432, \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u043f\u0430\u0440\u043a). \u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044f\u044f TMS \u2014 ~500 \u0432\u043e\u0434\u0438\u0442\u0435\u043b\u0435\u0439 + ~50 \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u043e\u0432, 3-\u043d\u043e\u0434\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0442\u0435\u0440 (Xeon, 4 \u044f\u0434\u0440\u0430 \/ 8 \u0413\u0411 \/ 50 \u0413\u0411 SSD \u043d\u0430 \u043d\u043e\u0434\u0443), ~3 \u043c\u0435\u0441\u044f\u0446\u0430 \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b, 10\u201315% CPU \u043f\u043e\u0434 \u043f\u043e\u043b\u043d\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439. \u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 redb.Route: SAP, Kafka, RabbitMQ, GPS-\u0444\u0438\u0434\u044b, \u041c\u0435\u0440\u043a\u0443\u0440\u0438\u0439, \u0415\u0413\u0410\u0418\u0421, \u0427\u0435\u0441\u0442\u043d\u044b\u0439 \u0417\u041d\u0410\u041a, \u0424\u0413\u0418\u0421 \u0417\u0435\u0440\u043d\u043e.\u0412\u0442\u043e\u0440\u043e\u0439 production-\u043f\u0440\u043e\u0434\u0443\u043a\u0442: \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 (~672k \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, ~8M \u0441\u0432\u043e\u0439\u0441\u0442\u0432). \u041d\u0438 \u043e\u0434\u043d\u043e\u0439 \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0437\u0430 \u0432\u0435\u0441\u044c \u0441\u0440\u043e\u043a \u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0430\u0446\u0438\u0438. \u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u0435 \u0432 \u043c\u043e\u0434\u0435\u043b\u044c \u2014 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u0432 C#-\u043a\u043b\u0430\u0441\u0441 \u2192 SyncSchemeAsync() \u2192 \u0433\u043e\u0442\u043e\u0432\u043e.\u041a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432 \u043a\u043e\u0434\u0435\u0412\u043e\u0442 \u0440\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0438\u0437 redb.Examples:[RedbScheme(&#171;Employee&#187;)]public class EmployeeProps{    public string FirstName { get; set; } = &#171;&#187;;    public string LastName { get; set; } = &#171;&#187;;    public int Age { get; set; }    public decimal Salary { get; set; }    public string Department { get; set; } = &#171;&#187;;    public DateTime HireDate { get; set; }    public string[]? Skills { get; set; }    public Address? HomeAddress { get; set; }    public Contact[]? Contacts { get; set; }    public RedbObject&lt;ProjectMetricsProps&gt;? CurrentProject { get; set; }    public RedbObject&lt;ProjectMetricsProps&gt;[]? PastProjects { get; set; }    public Dictionary&lt;int, decimal&gt;? BonusByYear { get; set; }    public Dictionary&lt;string, Department&gt;? DepartmentHistory { get; set; }}\u042d\u0442\u043e \u0432\u0441\u044f \u0441\u0445\u0435\u043c\u0430. \u0412\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b, \u043c\u0430\u0441\u0441\u0438\u0432\u044b, \u0441\u043b\u043e\u0432\u0430\u0440\u0438, \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 RedbObject \u2014 \u0432\u0441\u0451 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c:var employee = new RedbObject&lt;EmployeeProps&gt;{    name = &#171;Alice Johnson&#187;,    Props = new EmployeeProps    {        FirstName = &#171;Alice&#187;,        LastName = &#171;Johnson&#187;,        Age = 28,        Salary = 85000m,        Skills = [&#171;C#&#187;, &#171;React&#187;, &#171;SQL&#187;]    }};await redb.SaveAsync(employee);\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c:var loaded = await redb.LoadAsync&lt;EmployeeProps&gt;(id);\/\/ loaded.Props.FirstName \u2192 &#171;Alice&#187;\/\/ loaded.Props.CurrentProject.Props \u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\/\/ loaded.Props.Contacts[0].Value \u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0417\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c:var results = await redb.Query&lt;EmployeeProps&gt;()    .Where(e =&gt; e.Salary &gt; 75000m &amp;&amp; e.Skills.Contains(&#171;C#&#187;))    .OrderByDescending(e =&gt; e.Salary)    .Take(100)    .ToListAsync();\u0417\u0430\u0431\u044b\u0442\u044c Include \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u2014 Props \u0432\u0441\u0435\u0433\u0434\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d \u0446\u0435\u043b\u0438\u043a\u043e\u043c. \u041d\u0443\u0436\u043d\u0430 \u043f\u0440\u043e\u0435\u043a\u0446\u0438\u044f? \u0422\u043e\u0433\u0434\u0430 .Select():var projected = await redb.Query&lt;EmployeeProps&gt;()    .Select(x =&gt; new { x.Props.FirstName, x.Props.Salary })    .ToListAsync();\u0421\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0435: \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u0438\u0437 28 \u0442\u0430\u0431\u043b\u0438\u0446\u0412 EF Core:var order = await context.Orders    .Include(o =&gt; o.Customer)    .Include(o =&gt; o.Items).ThenInclude(i =&gt; i.Product)        .ThenInclude(p =&gt; p.Category)    .Include(o =&gt; o.Items).ThenInclude(i =&gt; i.Discounts)    .Include(o =&gt; o.Shipping).ThenInclude(s =&gt; s.Address)    .Include(o =&gt; o.Payment).ThenInclude(p =&gt; p.Transactions)    \/\/ &#8230; \u0435\u0449\u0451 35 \u0441\u0442\u0440\u043e\u043a Include &#8230;    .FirstOrDefaultAsync(o =&gt; o.Id == orderId);\u0412 RedBase:var order = await redb.LoadAsync&lt;OrderProps&gt;(orderId);\u041e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430. \u0412\u0441\u0435 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043c\u0430\u0441\u0441\u0438\u0432\u044b, \u0441\u043b\u043e\u0432\u0430\u0440\u0438 \u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u044b. \u0418 \u0431\u044b\u0441\u0442\u0440\u043e: \u0434\u0432\u0438\u0436\u043e\u043a \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 Props \u043e\u0434\u043d\u0438\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c \u043a \u043f\u043b\u043e\u0441\u043a\u043e\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435, \u0431\u0435\u0437 JOIN-\u043a\u0430\u0441\u043a\u0430\u0434\u0430 \u043f\u043e 28 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c. \u0412 Pro \u2014 \u0435\u0449\u0451 \u0431\u044b\u0441\u0442\u0440\u0435\u0435: \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0447\u0435\u0440\u0435\u0437 Parallel.ForEach, \u043a\u0430\u0436\u0434\u0430\u044f \u0432\u0435\u0442\u043a\u0430 \u0433\u0440\u0430\u0444\u0430 \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e.\u0410 \u0447\u0442\u043e \u0441 \u0434\u0435\u0440\u0435\u0432\u044c\u044f\u043c\u0438?\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043e \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438. \u041d\u0435 \u043d\u0443\u0436\u0435\u043d \u043d\u0438 closure table \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u043d\u0438 \u0440\u0435\u043a\u0443\u0440\u0441\u0438\u0432\u043d\u044b\u0435 CTE \u0432 raw SQL:\/\/ \u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0434\u0435\u0440\u0435\u0432\u043eawait redb.CreateChildAsync(department, parentDepartment);\/\/ \u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0432\u0441\u0451 \u0434\u0435\u0440\u0435\u0432\u043e (\u0434\u043e 5 \u0443\u0440\u043e\u0432\u043d\u0435\u0439)var tree = await redb.LoadTreeAsync&lt;DepartmentProps&gt;(rootId, maxDepth: 5);\/\/ LINQ \u043f\u043e \u0434\u0435\u0440\u0435\u0432\u0443var bigDepts = await redb.TreeQuery&lt;DepartmentProps&gt;()    .Where(d =&gt; d.Budget &gt; 500000m)    .WhereLevel(2)    .ToListAsync();\/\/ \u041d\u0430\u0439\u0442\u0438 \u0432\u0441\u0435\u0445, \u0443 \u043a\u043e\u0433\u043e \u043f\u0440\u0435\u0434\u043e\u043a \u0441 \u0431\u044e\u0434\u0436\u0435\u0442\u043e\u043c &gt; 1Mvar rich = await redb.TreeQuery&lt;DepartmentProps&gt;()    .WhereHasAncestor&lt;DepartmentProps&gt;(a =&gt; a.Budget &gt; 1_000_000m)    .ToListAsync();\u041e\u043a\u043e\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438? GroupBy? \u0410\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u0438?\u0422\u043e\u0436\u0435 \u0447\u0435\u0440\u0435\u0437 LINQ:\/\/ ROW_NUMBER() PARTITION BY Department ORDER BY Salary DESCvar ranked = await redb.Query&lt;EmployeeProps&gt;()    .WithWindow(w =&gt; w        .PartitionBy(x =&gt; x.Department)        .OrderByDesc(x =&gt; x.Salary))    .SelectAsync(x =&gt; new    {        Name = x.Props.FirstName,        Department = x.Props.Department,        Rank = Win.RowNumber()    });\/\/ GroupBy + \u0430\u0433\u0440\u0435\u0433\u0430\u0446\u0438\u044fvar stats = await redb.Query&lt;EmployeeProps&gt;()    .GroupBy(x =&gt; x.Department)    .SelectAsync(g =&gt; new    {        g.Key,        Total = Agg.Count(),        AvgSalary = Agg.Average(g, x =&gt; x.Salary)    });\u0427\u0442\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0434\u0432\u0438\u0436\u043e\u043a \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c\u041e\u0431\u044b\u0447\u043d\u043e SQL \u0437\u0430 LINQ-\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 \u0441\u043a\u0443\u0447\u043d\u044b\u0439. \u041d\u043e \u0432\u043e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043f\u043e\u0447\u0435\u043c\u0443 \u0437\u0434\u0435\u0441\u044c \u043d\u0443\u0436\u0435\u043d \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 query engine, \u0430 \u043d\u0435 \u00ab\u043f\u0440\u043e\u0441\u0442\u043e ORM\u00bb.\u041c\u043e\u0434\u0435\u043b\u044c:public class Address{    public string City   { get; set; } = string.Empty;    public string Street { get; set; } = string.Empty;}[RedbScheme(&#171;Employee&#187;)]public class EmployeeProps{    \/\/ &#8230;    public Dictionary&lt;string, Address&gt;? OfficeLocations { get; set; }}LINQ-\u0437\u0430\u043f\u0440\u043e\u0441 \u2014 \u043d\u0430\u0439\u0442\u0438 \u0432\u0441\u0435\u0445 \u0441\u043e\u0442\u0440\u0443\u0434\u043d\u0438\u043a\u043e\u0432, \u0443 \u043a\u043e\u0433\u043e HQ-\u043e\u0444\u0438\u0441 \u0432 \u041d\u044c\u044e-\u0419\u043e\u0440\u043a\u0435:var result = await redb.Query&lt;EmployeeProps&gt;()    .Where(e =&gt; e.OfficeLocations![&#171;HQ&#187;].City == &#171;New York&#187;)    .Take(100)    .ToListAsync();\u0427\u0442\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f PostgreSQL:&#8212; PVT CTE (nested-only optimization): OfficeLocations[HQ].CityWITH pvt_cte AS (  WITH  nested_dict_0 AS (    SELECT dp._id_object         , (array_agg(nv._string)              FILTER (WHERE nv._id_structure = $5))[1] AS &#171;OfficeLocations[HQ].City&#187;    FROM _values dp    LEFT JOIN _values nv           ON nv._array_parent_id = dp._id          AND nv._id_structure = $5    WHERE dp._id_structure = $3       &#8212; \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0441\u043b\u043e\u0432\u0430\u0440\u044f OfficeLocations      AND dp._array_index  = $4       &#8212; \u043a\u043b\u044e\u0447 &#171;HQ&#187;      AND dp._id_object IN (            SELECT _id FROM _objects WHERE _id_scheme = $1          )    GROUP BY dp._id_object  )  SELECT nd0._id_object       , nd0.&#187;OfficeLocations[HQ].City&#187;  FROM nested_dict_0 nd0  WHERE nd0.&#187;OfficeLocations[HQ].City&#187; = $2   &#8212; &#171;New York&#187;)SELECT o.*FROM _objects oJOIN pvt_cte ON pvt_cte._id_object = o._idWHERE o._id_scheme = $1LIMIT 100\u0427\u0442\u043e \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f MSSQL:&#8212; PVT CTE (nested MAX CASE WHEN): OfficeLocations[HQ].City;WITHraw_values AS (  SELECT nv._array_parent_id AS _parent_id       , MAX(CASE WHEN nv._id_structure = 1010067                  THEN nv._string END) AS [OfficeLocations$LHQ$R$DCity]  FROM _values nv  WHERE nv._id_structure IN (1010067)    AND nv._array_parent_id IS NOT NULL  GROUP BY nv._array_parent_id),pvt_cte AS (  SELECT dp._id_object       , rv.[OfficeLocations$LHQ$R$DCity]  FROM _values dp  JOIN raw_values rv ON rv._parent_id = dp._id  WHERE dp._id_structure = @p2      &#8212; \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0441\u043b\u043e\u0432\u0430\u0440\u044f OfficeLocations    AND dp._array_index  = @p3      &#8212; \u043a\u043b\u044e\u0447 &#171;HQ&#187;    AND dp._id_object IN (          SELECT _id FROM _objects WHERE _id_scheme = @p0        )    AND rv.[OfficeLocations$LHQ$R$DCity] = @p1   &#8212; &#171;New York&#187;)SELECT o.*FROM _objects oJOIN pvt_cte ON pvt_cte._id_object = o._idWHERE o._id_scheme = @p0ORDER BY o._idOFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY\u041e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 LINQ \u2014 \u0434\u0432\u0430 \u0434\u0438\u0430\u043b\u0435\u043a\u0442\u0430, \u0434\u0432\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u043a \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438 (PostgreSQL \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 array_agg FILTER, MSSQL \u2014 MAX CASE WHEN). SQL \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0434 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u0434\u0438\u0430\u043b\u0435\u043a\u0442 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438. \u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0447\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u043e\u0441\u044c \u0432\u0441\u0435\u0433\u0434\u0430 \u043c\u043e\u0436\u043d\u043e \u0447\u0435\u0440\u0435\u0437 .ToSqlStringAsync().\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u2014 5 \u0441\u0442\u0440\u043e\u043aPostgreSQL \u0438\u043b\u0438 MSSQL \u2014 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u043e\u0439:\/\/ PostgreSQL + Pro\/\/ jit=off \u2014 \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u043c JIT-\u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u044e PostgreSQL, \u043d\u0430 \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u0445 \u043e\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043c\u0435\u0434\u043b\u044f\u0435\u0442builder.Services.AddRedbPro(options =&gt; options    .UsePostgres(&#171;Host=localhost;Database=mydb;Username=postgres;Password=pass;Options=-c jit=off&#187;)    .WithLicense(&#171;YOUR-LICENSE-KEY&#187;)    .Configure(c =&gt;    {        c.EnablePropsCache = true;        c.EnableLazyLoadingForProps = false;    }));\/\/ MSSQL + Probuilder.Services.AddRedbPro(options =&gt; options    .UseMsSql(&#171;Server=localhost;Database=mydb;Trusted_Connection=true&#187;)    .WithLicense(&#171;YOUR-LICENSE-KEY&#187;));\/\/ Free (Apache 2.0) \u2014 PostgreSQLbuilder.Services.AddRedb(options =&gt; options    .UsePostgres(&#171;Host=localhost;Database=mydb;Username=postgres;Password=pass;Options=-c jit=off&#187;));\/\/ Free (Apache 2.0) \u2014 MSSQLbuilder.Services.AddRedb(options =&gt; options    .UseMsSql(&#171;Server=localhost;Database=mydb;Trusted_Connection=true&#187;));Free vs Pro\u042f\u0434\u0440\u043e \u2014 open source, Apache 2.0. PostgreSQL \u0438 MSSQL. \u041f\u043e\u043b\u043d\u044b\u0439 LINQ, \u0434\u0435\u0440\u0435\u0432\u044c\u044f, \u0441\u043f\u0438\u0441\u043a\u0438, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438, \u044d\u043a\u0441\u043f\u043e\u0440\u0442\/\u0438\u043c\u043f\u043e\u0440\u0442.\u041e\u0431\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0438 \u0441 PostgreSQL, \u0438 \u0441 MSSQL. \u0423 \u0434\u0432\u0438\u0436\u043a\u0430 \u0435\u0434\u0438\u043d\u044b\u0439 SQL-\u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u2014 \u043f\u0435\u0440\u0435\u0435\u0437\u0434 \u043c\u0435\u0436\u0434\u0443 \u0431\u0430\u0437\u0430\u043c\u0438 \u044d\u0442\u043e \u0441\u043c\u0435\u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 (.UsePostgres() \u2194 .UseMsSql()). \u0410 redb.Export \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u043e\u0434\u043d\u043e\u0439 \u0431\u0430\u0437\u044b \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u0434\u0440\u0443\u0433\u0443\u044e \u2014 PostgreSQL \u2192 MSSQL \u0438 \u043e\u0431\u0440\u0430\u0442\u043d\u043e.Pro \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c:Compiled queries \u2014 LINQ \u043a\u043e\u043c\u043f\u0438\u043b\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0432 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 SQL, \u0431\u0435\u0437 JSON-\u0438\u043d\u0442\u0435\u0440\u043f\u0440\u0435\u0442\u0430\u0442\u043e\u0440\u0430Parallel materialization \u2014 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 Props \u0447\u0435\u0440\u0435\u0437 Parallel.ForEachChange tracking \u2014 \u0443\u043c\u043d\u043e\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435: \u0441\u0442\u0440\u043e\u044f\u0442\u0441\u044f &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-481870","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/481870","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=481870"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/481870\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=481870"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=481870"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=481870"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}