Искать строки согласования участника через Search с откатом на REST

Один запрос Search на сайт находит кандидатов по согласующему и статусам,
дальше читаются только списки, где они нашлись. При ошибке Search — прежний
обход партиций по REST.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-10-02 15:36:01 +05:00
parent feb95f5621
commit 4285a84005
2 changed files with 216 additions and 8 deletions
@@ -54,6 +54,15 @@ public interface ISharePointClient
Task<IReadOnlyList<JsonElement>> GetItemsByListTitleAsync(
string siteUrl, string listTitle, string odataQuery, CancellationToken cancellationToken);
/// <summary>
/// Запрос в SharePoint Search (postquery) от имени приложения — видит ВСЕ
/// элементы, а не только доступные пользователю. Возвращает строки результата
/// как «имя managed property → значение» (RowLimit — не больше 500).
/// Индекс отстаёт от списков, поэтому найденное нужно перепроверять по REST.
/// </summary>
Task<IReadOnlyList<IReadOnlyDictionary<string, string?>>> SearchAsync(
string siteUrl, string queryText, IReadOnlyList<string> selectProperties, CancellationToken cancellationToken);
/// <summary>
/// Обновить поля элемента списка, найденного по заголовку. fields — анонимный
/// объект «внутреннее имя колонки → значение»; перечисленные колонки
@@ -220,6 +229,57 @@ public sealed class SharePointClient(
$"{siteUrl.TrimEnd('/')}/_api/web/lists/getByTitle('{Escape(listTitle)}')/items?{odataQuery}",
cancellationToken);
public async Task<IReadOnlyList<IReadOnlyDictionary<string, string?>>> SearchAsync(
string siteUrl, string queryText, IReadOnlyList<string> selectProperties, CancellationToken cancellationToken)
{
var body = JsonSerializer.Serialize(new
{
request = new
{
Querytext = queryText,
RowLimit = 500,
SelectProperties = selectProperties,
TrimDuplicates = false
}
});
var json = await SendAsync(
HttpMethod.Post, siteUrl, $"{siteUrl.TrimEnd('/')}/_api/search/postquery", body,
allowNotFound: false, cancellationToken);
var result = new List<IReadOnlyDictionary<string, string?>>();
if (json is null
|| !json.Value.TryGetProperty("PrimaryQueryResult", out var primary)
|| !primary.TryGetProperty("RelevantResults", out var relevant)
|| !relevant.TryGetProperty("Table", out var table)
|| !table.TryGetProperty("Rows", out var rows))
return result;
// nometadata отдаёт Rows массивом, verbose — обёрнутым в results; терпим оба.
if (rows.ValueKind == JsonValueKind.Object && rows.TryGetProperty("results", out var wrapped))
rows = wrapped;
if (rows.ValueKind != JsonValueKind.Array) return result;
foreach (var row in rows.EnumerateArray())
{
if (!row.TryGetProperty("Cells", out var cells)) continue;
if (cells.ValueKind == JsonValueKind.Object && cells.TryGetProperty("results", out var wrappedCells))
cells = wrappedCells;
var dict = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
foreach (var cell in cells.EnumerateArray())
{
var key = cell.TryGetProperty("Key", out var k) ? k.GetString() : null;
if (key is null) continue;
dict[key] = cell.TryGetProperty("Value", out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() : null;
}
result.Add(dict);
}
return result;
}
public Task UpdateItemByListTitleAsync(
string siteUrl, string listTitle, int itemId, object fields, CancellationToken cancellationToken) =>
MergeAsync(