diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/CrmQueryDispatcher.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/CrmQueryDispatcher.cs index 2b7c008d6..765186326 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/CrmQueryDispatcher.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/CrmQueryDispatcher.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.PowerPlatform.Dataverse.Client; @@ -20,6 +21,24 @@ public async Task ExecuteQuery(ICrmQuery query) return await wrappedHandler.Execute(query, organizationService); } + public async IAsyncEnumerable ExecuteQuery(IEnumerableCrmQuery query, [EnumeratorCancellation] CancellationToken cancellationToken) + { + using var scope = serviceProvider.CreateScope(); + + var organizationService = scope.ServiceProvider.GetRequiredKeyedService(serviceClientName); + + var handlerType = typeof(IEnumerableCrmQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult)); + var handler = scope.ServiceProvider.GetRequiredService(handlerType); + + var wrapperHandlerType = typeof(EnumerableQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult)); + var wrappedHandler = (EnumerableQueryHandler)Activator.CreateInstance(wrapperHandlerType, handler)!; + + await foreach (var result in wrappedHandler.Execute(query, organizationService, cancellationToken)) + { + yield return result; + } + } + private abstract class QueryHandler { public abstract Task Execute(ICrmQuery query, IOrganizationServiceAsync organizationService); @@ -35,4 +54,21 @@ public override Task Execute( return innerHandler.Execute((TQuery)query, organizationService); } } + + private abstract class EnumerableQueryHandler + { + public abstract IAsyncEnumerable Execute(IEnumerableCrmQuery query, IOrganizationServiceAsync organizationService, CancellationToken cancellationToken); + } + + private class EnumerableQueryHandler(IEnumerableCrmQueryHandler innerHandler) : EnumerableQueryHandler + where TQuery : IEnumerableCrmQuery + { + public override IAsyncEnumerable Execute( + IEnumerableCrmQuery query, + IOrganizationServiceAsync organizationService, + CancellationToken cancellationToken) + { + return innerHandler.Execute((TQuery)query, organizationService, cancellationToken); + } + } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/ICrmQueryDispatcher.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/ICrmQueryDispatcher.cs index a5ae1e06e..8858c778d 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/ICrmQueryDispatcher.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/ICrmQueryDispatcher.cs @@ -3,4 +3,5 @@ namespace TeachingRecordSystem.Core.Dqt; public interface ICrmQueryDispatcher { Task ExecuteQuery(ICrmQuery query); + IAsyncEnumerable ExecuteQuery(IEnumerableCrmQuery query, CancellationToken cancellationToken = default); } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/IEnumerableCrmQuery.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/IEnumerableCrmQuery.cs new file mode 100644 index 000000000..d7dfc56ac --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/IEnumerableCrmQuery.cs @@ -0,0 +1,5 @@ +namespace TeachingRecordSystem.Core.Dqt; + +public interface IEnumerableCrmQuery +{ +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/IEnumerableCrmQueryHandler.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/IEnumerableCrmQueryHandler.cs new file mode 100644 index 000000000..a1cbc3a1f --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/IEnumerableCrmQueryHandler.cs @@ -0,0 +1,9 @@ +using Microsoft.PowerPlatform.Dataverse.Client; + +namespace TeachingRecordSystem.Core.Dqt; + +public interface IEnumerableCrmQueryHandler + where TQuery : IEnumerableCrmQuery +{ + IAsyncEnumerable Execute(TQuery query, IOrganizationServiceAsync organizationService, CancellationToken cancellationToken); +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetNonOpenTaskAnnotationsQuery.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetNonOpenTaskAnnotationsQuery.cs index 7060946bc..2a99680f4 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetNonOpenTaskAnnotationsQuery.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetNonOpenTaskAnnotationsQuery.cs @@ -2,4 +2,4 @@ namespace TeachingRecordSystem.Core.Dqt.Queries; -public record GetNonOpenTaskAnnotationsQuery(string[] Subjects, DateTime ModifiedBefore, ColumnSet ColumnSet) : ICrmQuery; +public record GetNonOpenTaskAnnotationsQuery(string[] Subjects, DateTime ModifiedBefore, ColumnSet ColumnSet) : IEnumerableCrmQuery; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetResolvedIncidentAnnotationsQuery.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetResolvedIncidentAnnotationsQuery.cs index 5e135e9b2..8a78fcc0b 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetResolvedIncidentAnnotationsQuery.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/Queries/GetResolvedIncidentAnnotationsQuery.cs @@ -2,4 +2,4 @@ namespace TeachingRecordSystem.Core.Dqt.Queries; -public record GetResolvedIncidentAnnotationsQuery(Guid[] SubjectIds, DateTime ModifiedBefore, ColumnSet ColumnSet) : ICrmQuery; +public record GetResolvedIncidentAnnotationsQuery(Guid[] SubjectIds, DateTime ModifiedBefore, ColumnSet ColumnSet) : IEnumerableCrmQuery; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetNonOpenTaskAnnotationsHandler.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetNonOpenTaskAnnotationsHandler.cs index 9abecd67a..ad1f7e98a 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetNonOpenTaskAnnotationsHandler.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetNonOpenTaskAnnotationsHandler.cs @@ -1,13 +1,18 @@ +using System.Runtime.CompilerServices; using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Query; using TeachingRecordSystem.Core.Dqt.Queries; namespace TeachingRecordSystem.Core.Dqt.QueryHandlers; -public class GetNonOpenTaskAnnotationsHandler : ICrmQueryHandler +public class GetNonOpenTaskAnnotationsHandler : IEnumerableCrmQueryHandler { - public async Task Execute(GetNonOpenTaskAnnotationsQuery query, IOrganizationServiceAsync organizationService) + public async IAsyncEnumerable Execute( + GetNonOpenTaskAnnotationsQuery query, + IOrganizationServiceAsync organizationService, + [EnumeratorCancellation] CancellationToken cancellationToken) { var queryExpression = new QueryExpression() { @@ -26,13 +31,33 @@ public async Task Execute(GetNonOpenTaskAnnotationsQuery query, IO taskLink.LinkCriteria.AddCondition(CrmTask.Fields.Subject, ConditionOperator.In, query.Subjects.Cast().ToArray()); queryExpression.LinkEntities.Add(taskLink); + queryExpression.PageInfo = new() + { + Count = 50, + PageNumber = 1 + }; + var request = new RetrieveMultipleRequest() { Query = queryExpression }; - var response = await organizationService.RetrieveMultipleAsync(queryExpression); + EntityCollection response; + do + { + cancellationToken.ThrowIfCancellationRequested(); + + response = await organizationService.RetrieveMultipleAsync(queryExpression); + + var annotations = response.Entities.Select(e => e.ToEntity()).ToArray(); + if (annotations.Length > 0) + { + yield return annotations; + } - return response.Entities.Select(e => e.ToEntity()).ToArray(); + queryExpression.PageInfo.PageNumber++; + queryExpression.PageInfo.PagingCookie = response.PagingCookie; + } + while (response.MoreRecords); } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetResolvedIncidentAnnotationsHandler.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetResolvedIncidentAnnotationsHandler.cs index da5e23b84..61c0be69d 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetResolvedIncidentAnnotationsHandler.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Dqt/QueryHandlers/GetResolvedIncidentAnnotationsHandler.cs @@ -1,13 +1,18 @@ +using System.Runtime.CompilerServices; using Microsoft.PowerPlatform.Dataverse.Client; +using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Query; using TeachingRecordSystem.Core.Dqt.Queries; namespace TeachingRecordSystem.Core.Dqt.QueryHandlers; -public class GetResolvedIncidentAnnotationsHandler : ICrmQueryHandler +public class GetResolvedIncidentAnnotationsHandler : IEnumerableCrmQueryHandler { - public async Task Execute(GetResolvedIncidentAnnotationsQuery query, IOrganizationServiceAsync organizationService) + public async IAsyncEnumerable Execute( + GetResolvedIncidentAnnotationsQuery query, + IOrganizationServiceAsync organizationService, + [EnumeratorCancellation] CancellationToken cancellationToken) { var queryExpression = new QueryExpression() { @@ -34,13 +39,33 @@ public async Task Execute(GetResolvedIncidentAnnotationsQuery quer incidentLink.LinkCriteria.AddCondition(Incident.Fields.SubjectId, ConditionOperator.In, query.SubjectIds.Cast().ToArray()); documentLink.LinkEntities.Add(incidentLink); + queryExpression.PageInfo = new() + { + Count = 50, + PageNumber = 1 + }; + var request = new RetrieveMultipleRequest() { Query = queryExpression }; - var response = await organizationService.RetrieveMultipleAsync(queryExpression); + EntityCollection response; + do + { + cancellationToken.ThrowIfCancellationRequested(); + + response = await organizationService.RetrieveMultipleAsync(queryExpression); + + var annotations = response.Entities.Select(e => e.ToEntity()).ToArray(); + if (annotations.Length > 0) + { + yield return annotations; + } - return response.Entities.Select(e => e.ToEntity()).ToArray(); + queryExpression.PageInfo.PageNumber++; + queryExpression.PageInfo.PagingCookie = response.PagingCookie; + } + while (response.MoreRecords); } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/DeleteOldIncidentAttachmentsJob.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/DeleteOldIncidentAttachmentsJob.cs index 96f36d9ed..2328d12ec 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/DeleteOldIncidentAttachmentsJob.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/DeleteOldIncidentAttachmentsJob.cs @@ -16,13 +16,19 @@ public async Task Execute(CancellationToken cancellationToken) var modifiedBefore = clock.UtcNow.Subtract(_modifiedBeforeWindow); - var incidentAnnotations = await crmQueryDispatcher.ExecuteQuery( - new GetResolvedIncidentAnnotationsQuery(SubjectIds: [changeDateOfBirthSubject.Id, changeNameSubject.Id], modifiedBefore, ColumnSet: new())); + var annotationIds = new List(); - var taskAnnotations = await crmQueryDispatcher.ExecuteQuery( - new GetNonOpenTaskAnnotationsQuery(Subjects: [CreateTrnRequestTaskQuery.TaskSubject], modifiedBefore, ColumnSet: new())); + await foreach (var annotations in crmQueryDispatcher.ExecuteQuery( + new GetResolvedIncidentAnnotationsQuery(SubjectIds: [changeDateOfBirthSubject.Id, changeNameSubject.Id], modifiedBefore, ColumnSet: new()), cancellationToken)) + { + annotationIds.AddRange(annotations.Select(i => i.AnnotationId!.Value)); + } - var annotationIds = incidentAnnotations.Select(i => i.AnnotationId!.Value).Concat(taskAnnotations.Select(i => i.AnnotationId!.Value)); + await foreach (var annotations in crmQueryDispatcher.ExecuteQuery( + new GetNonOpenTaskAnnotationsQuery(Subjects: [CreateTrnRequestTaskQuery.TaskSubject], modifiedBefore, ColumnSet: new()), cancellationToken)) + { + annotationIds.AddRange(annotations.Select(i => i.AnnotationId!.Value)); + } foreach (var annotationId in annotationIds) { diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/CrmQueryDispatcherSpy.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/CrmQueryDispatcherSpy.cs index e616578e6..cfb8ace4f 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/CrmQueryDispatcherSpy.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.TestCommon/CrmQueryDispatcherSpy.cs @@ -47,4 +47,9 @@ public async Task ExecuteQuery(ICrmQuery query) spy.RegisterQuery(query, result); return result; } + + public IAsyncEnumerable ExecuteQuery(IEnumerableCrmQuery query, CancellationToken cancellationToken = default) + { + return innerDispatcher.ExecuteQuery(query, cancellationToken); + } }