Refactored the pagination, and removed the need for a FilterSelector, the KeySelector now covers the code properly.

This commit is contained in:
Colin Dawson 2026-02-04 22:26:10 +00:00
parent 7a6ff96ef8
commit 500dafe7a2
17 changed files with 105 additions and 315 deletions

View File

@ -46,7 +46,7 @@ public class BlockedIPsManager : IBlockedIPsManager
});
var paginatedData = await PaginatedData.Paginate<BlockedIPs, BlockedIPs>(data, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
return paginatedData;
}
@ -56,18 +56,6 @@ public class BlockedIPsManager : IBlockedIPsManager
return Math.Floor(attemptedTime.AddMinutes(loginAttemptTimeoutMinutes).Subtract(dateTimeNow).TotalMinutes);
}
private Expression<Func<BlockedIPs, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"ipaddress" => x => x.IpAddress.ToString().Contains(value),
"numberOfAttempts" => x => x.NumberOfAttempts.ToString().Contains(value),
"blockedAt" => x => x.BlockedAt.ToString().Contains(value),
"unblockedin" => x => x.UnblockedIn.ToString().Contains(value),
_ => x => x.IpAddress.ToString().Contains(value)
};
}
private Expression<Func<BlockedIPs, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -30,24 +30,10 @@ public class AuditLog : IAuditLog
var auditEntries = _auditLogRepository.GetAuditEntries(auditParams, primaryOnly);
var paginatedData =
await PaginatedData.Paginate<AuditEntry, AuditLogEntry>(auditEntries, paging, KeySelector, FilterSelector, cancellationToken);
await PaginatedData.Paginate<AuditEntry, AuditLogEntry>(auditEntries, paging, KeySelector, cancellationToken);
return paginatedData;
}
private Expression<Func<AuditEntry, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"userdisplayname" => x => x.AuditLog.UserDisplayName.Contains(value),
"type" => x => x.AuditLog.Type.Contains(value),
"comment" => x => x.AuditLog.Comment.Contains(value),
"displayname" => x => x.DisplayName!.Contains(value),
_ => x => true
};
}
private Expression<Func<AuditEntry, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -125,23 +125,11 @@ public class CustomFieldManager : ICustomFieldManager
var sequences = _customFieldRepository.GetCustomFieldList();
var paginatedData = await PaginatedData.Paginate<CustomField, CustomFieldDefinition>(sequences, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
return paginatedData;
}
private Expression<Func<CustomField, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"guid" => x => x.Guid.ToString().Contains(value),
"fieldtype" => x => x.FieldType.ToString().Contains(value),
"defaultvalue" => x => x.DefaultValue.ToString().Contains(value),
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<CustomField, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -27,22 +27,11 @@ namespace e_suite.Modules.DomainManager
var forms = _domainRepository.GetDomains();
var paginatedData = await PaginatedData.Paginate<Domain, GetDomain>(forms, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
return paginatedData;
}
private Expression<Func<Domain, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"guid" => x => x.Guid.ToString().Contains(value),
"name" => x => x.Name.Contains(value),
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<Domain, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -25,28 +25,11 @@ public class ExceptionLogger : IExceptionLogManager
var exceptionLogs = _exceptionLogRepository.GetExceptionLogs();
var paginatedData = await PaginatedData.Paginate<e_suite.Database.Core.Tables.Diagnostics.ExceptionLog, ExceptionLog>(exceptionLogs, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
return paginatedData;
}
[ExcludeFromCodeCoverage]
private Expression<Func<Database.Core.Tables.Diagnostics.ExceptionLog, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"application" => x => x.Application.ToString().Contains(value),
"exceptionJson" => x => x.ExceptionJson.ToString().Contains(value),
"message" => x => x.Message.ToString().Contains(value),
"stackTrace" => x => x.StackTrace.ToString().Contains(value),
"supportingData" => x => x.SupportingData.ToString().Contains(value),
"occuredAt" => x => x.OccuredAt.ToString().Contains(value),
_ => x => x.Id.ToString().Contains(value)
};
}
[ExcludeFromCodeCoverage]
private Expression<Func<Database.Core.Tables.Diagnostics.ExceptionLog, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -135,7 +135,7 @@ public class FormsManager : IFormsManager
var forms = _formRepository.GetTemplates();
var paginatedData = await PaginatedData.Paginate(forms, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
var mappedData = new List<GetFormTemplate>();
@ -152,21 +152,6 @@ public class FormsManager : IFormsManager
return paginatedResult;
}
private Expression<Func<FormTemplate, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"guid" => x => x.Guid.ToString().Contains(value),
"version" => x => x.Versions
.Where(v => !v.Deleted)
.Max(v => v.Version)
.ToString()
.Contains(value),
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<FormTemplate, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -40,22 +40,12 @@ public class MailTemplateManager : IMailTemplateManager
var queriableMailTemplateTypes = mailTemplateTypes.BuildMock();
var paginatedResult = await PaginatedData.Paginate(queriableMailTemplateTypes, paging, KeySelector, FilterSelector,
var paginatedResult = await PaginatedData.Paginate(queriableMailTemplateTypes, paging, KeySelector,
cancellationToken);
return paginatedResult;
}
private Expression<Func<MailTemplateType, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"description" => x => x.Description!.Contains(value),
"mailtype" => x => x.MailType.ToString().Contains(value),
_ => x => x.MailType.ToString().Contains(value)
};
}
private Expression<Func<MailTemplateType, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -58,24 +58,11 @@ public class OrganisationsManager : IOrganisationsManager
var organisations = _organisationsManagerRepository.GetOrganisationsList();
var paginatedData = await PaginatedData.Paginate<Organisation, ReadOrganisation>(organisations, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
return paginatedData;
}
private Expression<Func<Organisation, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"guid" => x => x.Guid.ToString().Contains(value),
"name" => x => x.Name.Contains(value),
"address" => x => x.Address.Contains(value),
"status" => x => x.Status.ToString().ToLower().Contains(value),
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<Organisation, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -22,7 +22,7 @@ public class PerformanceManager : IPerformanceManager
var paginatedData = await PaginatedData.Paginate(performanceReportSummary, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
var paginatedResult = new PaginatedData<ReadPerformanceReportSummary>
{
@ -46,23 +46,6 @@ public class PerformanceManager : IPerformanceManager
};
}
private Expression<Func<PerformanceReportSummary, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"host" => x => x.Host.Contains(value, StringComparison.CurrentCultureIgnoreCase),
"controllername" => x => x.ControllerName.Contains(value, StringComparison.CurrentCultureIgnoreCase),
"actionname" => x => x.ActionName.Contains(value, StringComparison.CurrentCultureIgnoreCase),
"requestType" => x => x.RequestType.Contains(value, StringComparison.CurrentCultureIgnoreCase),
"count" => x => x.Count.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
"mintotaltimems" => x => x.MinTotalTimeMs.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
"maxtotaltimemS" => x => x.MaxTotalTimeMs.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
"avgtotaltimems" => x => x.AvgTotalTimeMs.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
"sumtotaltimems" => x => x.SumTotalTimeMs.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
_ => x => x.SumTotalTimeMs.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
};
}
private Expression<Func<PerformanceReportSummary, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch
@ -92,7 +75,7 @@ public class PerformanceManager : IPerformanceManager
var performanceReportSummary = _performanceManagerRepository.GetPerformanceReports(hostName, controllerName, actionName, requestType);
var paginatedData = await PaginatedData.Paginate(performanceReportSummary, paging,
PerformanceReportKeySelector, PerformanceReportFilterSelector, cancellationToken);
PerformanceReportKeySelector, cancellationToken);
var paginatedResult = new PaginatedData<ReadPerformanceReport>
{
@ -132,23 +115,4 @@ public class PerformanceManager : IPerformanceManager
_ => x => x.StartDateTime
};
}
private Expression<Func<PerformanceReport, bool>> PerformanceReportFilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"host" => x => x.Host.Contains(value, StringComparison.CurrentCultureIgnoreCase),
"controllername" => x => x.ControllerName.Contains(value, StringComparison.CurrentCultureIgnoreCase),
"actionname" => x => x.ActionName.Contains(value, StringComparison.CurrentCultureIgnoreCase),
"requestType" => x => x.RequestType.Contains(value, StringComparison.CurrentCultureIgnoreCase),
"id" => x => x.Id.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
"actionparameters" => x =>
x.ActionParameters.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
"startdateyime" => x =>
x.StartDateTime.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
"timings" => x => x.Timings.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
"totaltimems" => x => x.TotalTimeMS.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase),
_ => x => x.StartDateTime.ToString().Contains(value, StringComparison.CurrentCultureIgnoreCase)
};
}
}

View File

@ -41,7 +41,7 @@ public class RoleManager : IRoleManager
}
var paginatedData = await PaginatedData.Paginate(roles, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
var paginatedResult = new PaginatedData<ReadRole>
{
@ -73,18 +73,6 @@ public class RoleManager : IRoleManager
DomainName = role.Domain.Name
};
}
private Expression<Func<Role, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"guid" => x => x.Guid.ToString().Contains(value),
"name" => x => x.Name.Contains(value),
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<Role, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch
@ -213,7 +201,7 @@ public class RoleManager : IRoleManager
}
var paginatedData = await PaginatedData.Paginate(roleUsers, paging,
UserRoleKeySelector, UserRoleFilterSelector, cancellationToken);
UserRoleKeySelector, cancellationToken);
var paginatedResult = new PaginatedData<RoleUser>
{
@ -237,16 +225,6 @@ public class RoleManager : IRoleManager
};
}
private Expression<Func<UserRole, bool>> UserRoleFilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"userid" => x => x.User.Id == long.Parse(value),
"displayname" => x => ((x.User.FirstName.Trim() + " " + x.User.MiddleNames).Trim() + " " + x.User.LastName).Trim().Contains(value),
_ => x => x.Id.ToString().Contains(value),
};
}
private Expression<Func<UserRole, object>> UserRoleKeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch
@ -300,23 +278,11 @@ public class RoleManager : IRoleManager
var accessList = EnumerateAccessList();
var paginatedResult = await PaginatedData.Paginate(accessList, paging,
AccessListKeySelector, AccessListFilterSelector, cancellationToken);
AccessListKeySelector, cancellationToken);
return paginatedResult;
}
private Expression<Func<GetSecurityAccess, bool>> AccessListFilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"securityaccess" => x => x.SecurityAccess.ToString().Contains(value),
"name" => x => x.Name.Contains(value),
"description" => x => x.Description.Contains(value),
"groupname" => x => x.GroupName.Contains(value),
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<GetSecurityAccess, object>> AccessListKeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch
@ -334,7 +300,7 @@ public class RoleManager : IRoleManager
var accessList = _roleManagerRepository.GetAccessForRole();
var paginatedData = await PaginatedData.Paginate(accessList, paging,
RoleAccessKeySelector, RoleAccessFilterSelector, cancellationToken);
RoleAccessKeySelector, cancellationToken);
var paginatedResult = new PaginatedData<GetRoleSecurityAccess>
{
@ -486,15 +452,6 @@ public class RoleManager : IRoleManager
return domainToCheck;
}
private Expression<Func<RoleAccess, bool>> RoleAccessFilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"roleid" => x => x.RoleId == long.Parse(value),
_ => x => x.AccessKey.ToString().Contains(value)
};
}
private Expression<Func<RoleAccess, object>> RoleAccessKeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -25,7 +25,7 @@ public class SsoManager : ISsoManager
var ssoProviders = _ssoManagerRepository.GetSsoProviders().Where(x => x.Deleted == false);
var paginatedData = await PaginatedData.Paginate(ssoProviders, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
var paginatedResult = new PaginatedData<ReadSsoProvider>
{
@ -61,17 +61,6 @@ public class SsoManager : ISsoManager
return readSsoProvider;
}
private Expression<Func<SsoProvider, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"name" => x => x.Name.Contains(value),
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<SsoProvider, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -54,7 +54,7 @@ public class SequenceManager : ISequenceManager
var sequences = _sequenceManagerRepository.GetSequenceList();
var paginatedData = await PaginatedData.Paginate(sequences, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
var paginatedResult = new PaginatedData<ReadSequence>()
{
@ -73,17 +73,6 @@ public class SequenceManager : ISequenceManager
return paginatedResult;
}
private Expression<Func<Database.Core.Tables.Sequences.Sequence, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"guid" => x => x.Guid.ToString().Contains(value),
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<Database.Core.Tables.Sequences.Sequence, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -41,7 +41,7 @@ public class SiteManager : ISiteManager
var paginatedData = await PaginatedData.Paginate(forms, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
var paginatedResult = new PaginatedData<ReadSite>
{
@ -53,17 +53,6 @@ public class SiteManager : ISiteManager
return paginatedResult;
}
private Expression<Func<Site, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"guid" => x => x.Guid.ToString().Contains(value),
"organisationid" => x => x.OrganisationId.ToString() == value,
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<Site, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -128,7 +128,7 @@ public class SpecificationManager : ISpecificationManager
var specifications = _specificationManagerRepository.GetSpecifications();
var paginatedData = await PaginatedData.Paginate(specifications, paging,
KeySelector, FilterSelector, cancellationToken);
KeySelector, cancellationToken);
var dataPage = new List<ReadSpecification>();
foreach (var data in paginatedData.Data)
@ -164,18 +164,6 @@ public class SpecificationManager : ISpecificationManager
};
}
private Expression<Func<Specification, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
"guid" => x => x.Guid.ToString().Contains(value),
"name" => x => x.Name.Contains(value),
"site.id" => x => x.Site.Id == long.Parse(value),
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<Specification, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch

View File

@ -644,7 +644,7 @@ public class UserManager : IUserManager
{
var users = _userManagerRepository.GetUsers().Where(x => x.Active == true);
var paginatedData = await PaginatedData.Paginate(users, paging, KeySelector, FilterSelector, cancellationToken);
var paginatedData = await PaginatedData.Paginate(users, paging, KeySelector, cancellationToken);
var paginatedResult = new PaginatedData<GetUser>
{
@ -656,6 +656,22 @@ public class UserManager : IUserManager
return paginatedResult;
}
private Expression<Func<User, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch
{
"firstname" => x => x.FirstName,
"lastname" => x => x.LastName,
"middlenames" => x => x.MiddleNames,
"displayname" => x => ((x.FirstName.Trim() + " " + x.MiddleNames).Trim() + " " + x.LastName).Trim(),
"domainname" => x => x.Domain.Name,
"created" => x => x.Created,
"lastupdated" => x => x.LastUpdated,
"email" => x => x.Email,
_ => x => x.Email
};
}
public async Task<GetUser?> GetUserAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken)
{
var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken)
@ -697,38 +713,6 @@ public class UserManager : IUserManager
return getUser;
}
private Expression<Func<User, bool>> FilterSelector(string? key, string value)
{
return key?.ToLowerInvariant() switch
{
"firstname" => x => x.FirstName.Contains(value),
"lastname" => x => x.LastName.Contains(value),
"middlenames" => x => x.MiddleNames.Contains(value),
"displayname" => x => ( x.FirstName + " " + x.MiddleNames + " " + x.LastName ).Contains(value),
"domainname" => x => x.Domain.Name.Contains(value),
"created" => x => x.Created.ToString().Contains(value),
"lastupdated" => x => x.LastUpdated.ToString().Contains(value),
"email" => x => x.Email.Contains(value),
_ => x => x.Email.Contains(value)
};
}
private Expression<Func<User, object>> KeySelector(string? sortKey)
{
return sortKey?.ToLowerInvariant() switch
{
"firstname" => x => x.FirstName,
"lastname" => x => x.LastName,
"middlenames" => x => x.MiddleNames,
"displayname" => x => x.FirstName + " " + x.MiddleNames + " " + x.LastName,
"domainname" => x => x.Domain.Name,
"created" => x => x.Created,
"lastupdated" => x => x.LastUpdated,
"email" => x => x.Email,
_ => x => x.Email
};
}
private void AddPasswordHash(User user,string tokenPass)
{
if (string.IsNullOrWhiteSpace(tokenPass))

View File

@ -13,15 +13,6 @@ public class TestData
[TestFixture]
public class PaginateUnitTests
{
private Expression<Func<TestData, bool>> FilterSelector(string key, string value)
{
return key.ToLowerInvariant() switch
{
"id" => x => x.Id.ToString().Contains(value),
_ => x => x.Name.Contains(value)
};
}
private Expression<Func<TestData, object>> KeySelector(string sortKey)
{
return sortKey.ToLowerInvariant() switch
@ -42,7 +33,7 @@ public class PaginateUnitTests
var queryable = data.BuildMock();
var paging = new Paging();
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(1));
Assert.That(result.Page, Is.EqualTo(1));
@ -71,7 +62,7 @@ public class PaginateUnitTests
var queryable = data.BuildMock();
var paging = new Paging();
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(10));
Assert.That(result.Page, Is.EqualTo(1));
@ -101,7 +92,7 @@ public class PaginateUnitTests
var queryable = data.BuildMock();
var paging = new Paging();
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(11));
Assert.That(result.Page, Is.EqualTo(1));
@ -134,7 +125,7 @@ public class PaginateUnitTests
{
Page = 2
};
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(11));
Assert.That(result.Page, Is.EqualTo(2));
@ -168,7 +159,7 @@ public class PaginateUnitTests
Page = 2
};
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(11));
Assert.That(result.Page, Is.EqualTo(2));
@ -207,7 +198,7 @@ public class PaginateUnitTests
Page = 2,
SortAscending = false
};
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(11));
Assert.That(result.Page, Is.EqualTo(2));
@ -248,7 +239,7 @@ public class PaginateUnitTests
SortAscending = false,
};
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(11));
Assert.That(result.Page, Is.EqualTo(2));
@ -289,7 +280,7 @@ public class PaginateUnitTests
SortAscending = true,
};
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(11));
Assert.That(result.Page, Is.EqualTo(2));
@ -328,7 +319,7 @@ public class PaginateUnitTests
Filters = "{\"id\":\"2\"}"
};
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(1));
Assert.That(result.Page, Is.EqualTo(1));
@ -367,7 +358,7 @@ public class PaginateUnitTests
Filters = "{\"name\":\"test2\"}"
};
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(1));
Assert.That(result.Page, Is.EqualTo(1));
@ -408,7 +399,7 @@ public class PaginateUnitTests
PageSize = 5
};
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, FilterSelector, CancellationToken.None);
var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);
Assert.That(result.Count, Is.EqualTo(11));
Assert.That(result.Page, Is.EqualTo(1));

View File

@ -19,11 +19,10 @@ public static class PaginatedData
IQueryable<T> queryable,
Paging paging,
Func<string, Expression<Func<T, object>>> keySelector,
Func<string, string, Expression<Func<T, bool>>> filterSelector,
CancellationToken cancellationToken
)
{
var filteredData = ApplyFilters(queryable, paging.Filters, filterSelector);
var filteredData = ApplyFilters(queryable, paging.Filters, keySelector);
var paginated = new PaginatedData<T>
{
@ -46,11 +45,10 @@ public static class PaginatedData
IQueryable<TInput> queryable,
Paging paging,
Func<string, Expression<Func<TInput, object>>> keySelector,
Func<string, string, Expression<Func<TInput, bool>>> filterSelector,
CancellationToken cancellationToken
)
{
var paged = await Paginate(queryable, paging, keySelector, filterSelector, cancellationToken);
var paged = await Paginate(queryable, paging, keySelector, cancellationToken);
var result = new PaginatedData<TOutput>
{
@ -67,12 +65,11 @@ public static class PaginatedData
IQueryable<TInput> queryable,
Paging paging,
Func<string, Expression<Func<TInput, object>>> keySelector,
Func<string, string, Expression<Func<TInput, bool>>> filterSelector,
CancellationToken cancellationToken,
params object[] args
)
{
var paged = await Paginate(queryable, paging, keySelector, filterSelector, cancellationToken);
var paged = await Paginate(queryable, paging, keySelector, cancellationToken);
var result = new PaginatedData<TOutput>
{
@ -92,8 +89,51 @@ public static class PaginatedData
}
private static IQueryable<T> ApplyFilters<T>(IQueryable<T> queryable, string filters,
Func<string, string, Expression<Func<T, bool>>> filterSelector)
private static Expression<Func<T, bool>> BuildContainsFilter<T>(
Expression<Func<T, object>> accessor,
string value)
{
var param = accessor.Parameters[0];
Expression body = accessor.Body;
// Unwrap boxing: (object)x.Id → x.Id
if (body is UnaryExpression unary && unary.NodeType == ExpressionType.Convert)
body = unary.Operand;
// If the accessor already returns a string, use it directly
if (body.Type == typeof(string))
{
// x => x.Property != null && x.Property.ToLower().Contains(value)
var notNull = Expression.NotEqual(body, Expression.Constant(null));
var toLower = Expression.Call(body, nameof(string.ToLower), Type.EmptyTypes);
var contains = Expression.Call(
toLower,
nameof(string.Contains),
Type.EmptyTypes,
Expression.Constant(value.ToLower())
);
var and = Expression.AndAlso(notNull, contains);
return Expression.Lambda<Func<T, bool>>(and, param);
}
// Otherwise: convert to string safely
var toString = Expression.Call(body, "ToString", Type.EmptyTypes);
var toLowerCall = Expression.Call(toString, nameof(string.ToLower), Type.EmptyTypes);
var containsCall = Expression.Call(
toLowerCall,
nameof(string.Contains),
Type.EmptyTypes,
Expression.Constant(value.ToLower())
);
return Expression.Lambda<Func<T, bool>>(containsCall, param);
}
private static IQueryable<T> ApplyFilters<T>(
IQueryable<T> queryable,
string filters,
Func<string, Expression<Func<T, object>>> keySelector)
{
if (string.IsNullOrWhiteSpace(filters))
return queryable;
@ -109,13 +149,16 @@ public static class PaginatedData
{
if (!string.IsNullOrWhiteSpace(filter.Value))
{
filteredQueryable = filteredQueryable.Where(filterSelector(filter.Key, filter.Value));
var accessor = keySelector(filter.Key);
var filterExpr = BuildContainsFilter(accessor, filter.Value);
filteredQueryable = filteredQueryable.Where(filterExpr);
}
}
return filteredQueryable;
}
private static IQueryable<T> ApplySort<T>(IQueryable<T> sequences, string sortKey, bool sortAscending,
Func<string, Expression<Func<T, object>>> keySelector)
{