diff --git a/Application/EdFi.Ods.Common/Infrastructure/Repositories/GetEntitiesBySpecification.cs b/Application/EdFi.Ods.Common/Infrastructure/Repositories/GetEntitiesBySpecification.cs index 513840d021..404b36d4ba 100644 --- a/Application/EdFi.Ods.Common/Infrastructure/Repositories/GetEntitiesBySpecification.cs +++ b/Application/EdFi.Ods.Common/Infrastructure/Repositories/GetEntitiesBySpecification.cs @@ -91,7 +91,7 @@ async Task GetPagedAggregateIdsAsync() var idQueryCriteria = _pagedAggregateIdsCriteriaProvider.GetCriteriaQuery(specification, queryParameters); SetChangeQueriesCriteria(idQueryCriteria); - queryBatch.Add(idQueryCriteria); + queryBatch.Add(idQueryCriteria); } // If requested, get a total count of available records @@ -100,17 +100,19 @@ async Task GetPagedAggregateIdsAsync() var countQueryCriteria = _totalCountCriteriaProvider.GetCriteriaQuery(specification, queryParameters); SetChangeQueriesCriteria(countQueryCriteria); - queryBatch.Add(countQueryCriteria); + queryBatch.Add(countQueryCriteria); } int resultIndex = 0; var ids = ItemsRequested() - ? await queryBatch.GetResultAsync(resultIndex++, cancellationToken) - : new Guid[0]; + ? (await queryBatch.GetResultAsync(resultIndex++, cancellationToken)) + .Select(r => (Guid) r[0]) + .ToArray() + : Array.Empty(); var totalCount = CountRequested() - ? (await queryBatch.GetResultAsync(resultIndex, cancellationToken)).First() + ? (await queryBatch.GetResultAsync(resultIndex, cancellationToken)).First() : 0; return new SpecificationResult @@ -141,7 +143,7 @@ void SetChangeQueriesCriteria(ICriteria criteria) private class SpecificationResult { public IList Ids { get; set; } - public long TotalCount { get; set; } + public int TotalCount { get; set; } } } } diff --git a/Application/EdFi.Ods.Common/Providers/Criteria/PagedAggregateIdsCriteriaProvider.cs b/Application/EdFi.Ods.Common/Providers/Criteria/PagedAggregateIdsCriteriaProvider.cs index f4c61770d6..ca6252d789 100644 --- a/Application/EdFi.Ods.Common/Providers/Criteria/PagedAggregateIdsCriteriaProvider.cs +++ b/Application/EdFi.Ods.Common/Providers/Criteria/PagedAggregateIdsCriteriaProvider.cs @@ -3,6 +3,7 @@ // The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. // See the LICENSE and NOTICES files in the project root for more information. +using System; using EdFi.Ods.Common.Caching; using NHibernate; using NHibernate.Criterion; @@ -18,8 +19,24 @@ public class PagedAggregateIdsCriteriaProvider : AggregateRootCriteriaP where TEntity : class { public PagedAggregateIdsCriteriaProvider(ISessionFactory sessionFactory, IDescriptorsCache descriptorsCache) - : base(sessionFactory, descriptorsCache) { } + : base(sessionFactory, descriptorsCache) + { + _identifierColumnNames = new Lazy( + () => + { + var persister = (AbstractEntityPersister) SessionFactory.GetClassMetadata(typeof(TEntity)); + + if (persister.IdentifierColumnNames != null && persister.IdentifierColumnNames.Length > 0) + { + return persister.IdentifierColumnNames; + } + + return new[] { "Id" }; + }); + } + private readonly Lazy _identifierColumnNames; + /// /// Get a query that retrieves the Ids for the next page of data. /// @@ -29,7 +46,7 @@ public PagedAggregateIdsCriteriaProvider(ISessionFactory sessionFactory, IDescri public ICriteria GetCriteriaQuery(TEntity specification, IQueryParameters queryParameters) { var idQueryCriteria = Session.CreateCriteria("aggregateRoot") - .SetProjection(Projections.Property("Id")) + .SetProjection(Projections.Distinct(GetColumnProjectionsForDistinctWithOrderBy())) .SetFirstResult(queryParameters.Offset ?? 0) .SetMaxResults(queryParameters.Limit ?? 25); @@ -42,22 +59,29 @@ public ICriteria GetCriteriaQuery(TEntity specification, IQueryParameters queryP ProcessQueryParameters(idQueryCriteria, queryParameters); return idQueryCriteria; - } - - private void AddDefaultOrdering(ICriteria queryCriteria) - { - var persister = (AbstractEntityPersister) SessionFactory.GetClassMetadata(typeof(TEntity)); - - if (persister.IdentifierColumnNames != null && persister.IdentifierColumnNames.Length > 0) + + IProjection GetColumnProjectionsForDistinctWithOrderBy() { - foreach (var identifierColumnName in persister.IdentifierColumnNames) + var projections = Projections.ProjectionList(); + + // Add the resource identifier (this is the value we need for the secondary "page" query) + projections.Add(Projections.Property("Id")); + + // Add the order by (primary key) columns (required when using DISTINCT with ORDER BY) + foreach (var identifierColumnName in _identifierColumnNames.Value) { - queryCriteria.AddOrder(Order.Asc(identifierColumnName)); + projections.Add(Projections.Property(identifierColumnName)); } + + return projections; } - else + } + + private void AddDefaultOrdering(ICriteria queryCriteria) + { + foreach (var identifierColumnName in _identifierColumnNames.Value) { - queryCriteria.AddOrder(Order.Asc("Id")); + queryCriteria.AddOrder(Order.Asc(identifierColumnName)); } } } diff --git a/Application/EdFi.Ods.Common/Providers/Criteria/TotalCountCriteriaProvider.cs b/Application/EdFi.Ods.Common/Providers/Criteria/TotalCountCriteriaProvider.cs index b47b0d0d31..0c99ff22bd 100644 --- a/Application/EdFi.Ods.Common/Providers/Criteria/TotalCountCriteriaProvider.cs +++ b/Application/EdFi.Ods.Common/Providers/Criteria/TotalCountCriteriaProvider.cs @@ -4,6 +4,7 @@ // See the LICENSE and NOTICES files in the project root for more information. using EdFi.Ods.Common.Caching; +using EdFi.Ods.Common.Models.Domain; using NHibernate; using NHibernate.Criterion; @@ -14,7 +15,7 @@ namespace EdFi.Ods.Common.Providers.Criteria /// /// The type of the entity to which criteria is being applied. public class TotalCountCriteriaProvider : AggregateRootCriteriaProviderBase, ITotalCountCriteriaProvider - where TEntity : class + where TEntity : AggregateRootWithCompositeKey { public TotalCountCriteriaProvider(ISessionFactory sessionFactory, IDescriptorsCache descriptorsCache) : base(sessionFactory, descriptorsCache) { } @@ -29,7 +30,7 @@ public ICriteria GetCriteriaQuery(TEntity specification, IQueryParameters queryP { var countQueryCriteria = Session .CreateCriteria("aggregateRoot") - .SetProjection(Projections.RowCountInt64()); + .SetProjection(Projections.CountDistinct(x => x.Id)); // Add specification-based criteria ProcessSpecification(countQueryCriteria, specification); diff --git a/Application/EdFi.Ods.Common/Repositories/GetBySpecificationResult.cs b/Application/EdFi.Ods.Common/Repositories/GetBySpecificationResult.cs index e55252c858..319881490a 100644 --- a/Application/EdFi.Ods.Common/Repositories/GetBySpecificationResult.cs +++ b/Application/EdFi.Ods.Common/Repositories/GetBySpecificationResult.cs @@ -17,7 +17,7 @@ public class GetBySpecificationResult public class ResultMetadata { - public long TotalCount { get; set; } + public int TotalCount { get; set; } public string CurrentPage { get; set; }