Skip to content

Commit 874e15f

Browse files
authored
Merge pull request #2 from tmacharia/stable
Bug fix to support Xml Serialization
2 parents fe5f1a5 + 2c8792a commit 874e15f

9 files changed

+282
-120
lines changed

PagedResult.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public PagedResult()
3636
/// <summary>
3737
/// Collection containing items in the current page.
3838
/// </summary>
39-
public IList<T> Items { get; set; }
39+
public List<T> Items { get; set; }
4040

4141
/// <summary>
4242
/// Calculates &amp; returns the hashcode of the current object.
@@ -53,7 +53,11 @@ public override int GetHashCode()
5353
/// <returns></returns>
5454
public override string ToString()
5555
{
56-
return string.Format("Page: {0:N0} Perpage: {1:N0} Totalpages: {2:N0} TotalItems: {3:N0}", Page, ItemsPerPage, TotalPages, TotalItems);
56+
return string.Format("{0:N0} items, Pg {1:N0}/{2:N0}, Total: {3:N0}",
57+
(Items != null && Items.Count > 0) ? Items.Count : ItemsPerPage,
58+
Page,
59+
TotalPages,
60+
TotalItems);
5761
}
5862
}
5963
}

Paginator.cs

+41-17
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ public static class Paginator
1616
internal const int DEF_PERPAGE = 10;
1717
internal const bool DEF_SKIPCOUNT = false;
1818

19+
/// <summary>
20+
/// Asynchronously lists items in the pagination request.
21+
/// </summary>
22+
/// <typeparam name="TEntity"></typeparam>
23+
/// <param name="query"></param>
24+
/// <param name="page">Page</param>
25+
/// <param name="perpage">Items per page.</param>
26+
/// <param name="cancellationToken">Cancellation token.</param>
27+
/// <returns>A task that represents the asynchronous operation which you can await.</returns>
28+
/// <exception cref="ArgumentException"/>
29+
/// <exception cref="ArgumentNullException"/>
30+
/// <exception cref="OperationCanceledException"/>
31+
/// <exception cref="ObjectDisposedException"/>
32+
public static Task<PagedResult<TEntity>> PaginateAsync<TEntity>(this IQueryable<TEntity> query, int page = DEF_PAGE, int perpage = DEF_PERPAGE, CancellationToken cancellationToken = default)
33+
where TEntity : class
34+
=> query.ProcessPaginationAsync(page, perpage, DEF_SKIPCOUNT, cancellationToken);
1935
/// <summary>
2036
/// Asynchronously lists items in the pagination request.
2137
/// </summary>
@@ -24,15 +40,16 @@ public static class Paginator
2440
/// <param name="page">Page</param>
2541
/// <param name="perpage">Items per page.</param>
2642
/// <param name="skipCount">Specify whether to omit running a count operation on your query againt the data store.</param>
27-
/// <param name="token">Cancellation token.</param>
43+
/// <param name="cancellationToken">Cancellation token.</param>
2844
/// <returns>A task that represents the asynchronous operation which you can await.</returns>
2945
/// <exception cref="ArgumentException"/>
3046
/// <exception cref="ArgumentNullException"/>
3147
/// <exception cref="OperationCanceledException"/>
48+
/// <exception cref="ObjectDisposedException"/>
3249
public static Task<PagedResult<TEntity>> PaginateAsync<TEntity>(this IQueryable<TEntity> query,
33-
int page = DEF_PAGE, int perpage = DEF_PERPAGE, bool skipCount = DEF_SKIPCOUNT, CancellationToken token = default)
50+
int page, int perpage, bool skipCount, CancellationToken cancellationToken = default)
3451
where TEntity : class
35-
=> query.ProcessPaginationAsync(page, perpage, skipCount, token);
52+
=> query.ProcessPaginationAsync(page, perpage, skipCount, cancellationToken);
3653
/// <summary>
3754
/// Lists items in the sequence and only returns the specified number.
3855
/// </summary>
@@ -49,21 +66,23 @@ public static PagedResult<TEntity> Paginate<TEntity>(this IQueryable<TEntity> qu
4966
where TEntity : class
5067
=> query.ProcessPagination(page, perpage, skipCount);
5168

69+
70+
5271
internal static async Task<PagedResult<TEntity>> ProcessPaginationAsync<TEntity>(this IQueryable<TEntity> query,
53-
int page = DEF_PAGE, int perpage = DEF_PERPAGE, bool skipCount = DEF_SKIPCOUNT, CancellationToken token = default)
72+
int page = DEF_PAGE, int perpage = DEF_PERPAGE, bool skipCount = DEF_SKIPCOUNT, CancellationToken cancellationToken = default)
5473
{
5574
ValidateParams_IfInvalid_Throw(page, perpage);
5675

5776
int total = 0;
5877
var list = new List<TEntity>();
5978

60-
token.ThrowIfCancellationRequested();
79+
cancellationToken.ThrowIfCancellationRequested();
6180

6281
if (!skipCount)
63-
total = await query.CountEntitiesAsync(token);
82+
total = await query.CountEntitiesAsync(cancellationToken);
6483

6584
if (skipCount || (!skipCount && total > 0))
66-
list = await query.Skip((page - 1) * perpage).Take(perpage).ToListAsync(token);
85+
list = await query.Skip((page - 1) * perpage).Take(perpage).ToListAsync(cancellationToken);
6786

6887
if (skipCount)
6988
total = list.Count;
@@ -104,19 +123,11 @@ internal static PagedResult<TEntity> ProcessPagination<TEntity>(this IQueryable<
104123
};
105124
}
106125

107-
internal static void ValidateParams_IfInvalid_Throw(int page, int perpage)
108-
{
109-
if (page <= 0)
110-
throw new ArgumentException("Page parameter must be greater than zero.", nameof(page));
111126

112-
if (perpage < 0)
113-
throw new ArgumentException("Per-page parameter must be 0 or greater than 0.", nameof(perpage));
114127

115-
return;
116-
}
117-
internal static Task<int> CountEntitiesAsync<TEntity>(this IQueryable<TEntity> query, CancellationToken token = default)
128+
internal static Task<int> CountEntitiesAsync<TEntity>(this IQueryable<TEntity> query, CancellationToken cancellationToken = default)
118129
{
119-
return query.CountAsync(token);
130+
return query.CountAsync(cancellationToken);
120131
}
121132
internal static int CalculateTotalPages(int totalItems, int perpage)
122133
{
@@ -128,5 +139,18 @@ internal static int CalculateTotalPages(int totalItems, int perpage)
128139
}
129140
return ans;
130141
}
142+
143+
144+
145+
internal static void ValidateParams_IfInvalid_Throw(int page, int perpage)
146+
{
147+
if (page <= 0)
148+
throw new ArgumentException("Page parameter must be greater than zero.", nameof(page));
149+
150+
if (perpage < 0)
151+
throw new ArgumentException("Per-page parameter must be 0 or greater than 0.", nameof(perpage));
152+
153+
return;
154+
}
131155
}
132156
}

tests/AsynchronousTests.cs

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using Paginator.EntityFrameworkCore;
2+
using NUnit.Framework;
3+
using System;
4+
using System.Threading.Tasks;
5+
using System.Threading;
6+
7+
namespace tests
8+
{
9+
[TestFixture]
10+
[SingleThreaded]
11+
internal class AsynchronousTests : TestBaseContext
12+
{
13+
[SetUp]
14+
public void Setup()
15+
{
16+
InitContx();
17+
}
18+
19+
#region Asynchronous
20+
[Order(0)]
21+
[TestCase(Category = ASYNC_TESTS)]
22+
public async Task AsyncPg_Empty()
23+
{
24+
var paged = await Context.Cars.PaginateAsync(1, 10);
25+
26+
Assert.IsNotNull(paged);
27+
Assert.Zero(paged.TotalItems);
28+
29+
Log(paged);
30+
}
31+
[Order(1)]
32+
[TestCase(Category = ASYNC_TESTS)]
33+
public void AsyncPg_Invalid_PageParam_ThrowEx()
34+
{
35+
Assert.ThrowsAsync<ArgumentException>(() => Context.Cars.PaginateAsync(0, 2));
36+
Assert.ThrowsAsync<ArgumentException>(() => Context.Cars.PaginateAsync(-1, 2));
37+
}
38+
[Order(1)]
39+
[TestCase(Category = ASYNC_TESTS)]
40+
public void AsyncPg_Invalid_PerPageParam_ThrowEx()
41+
{
42+
Assert.ThrowsAsync<ArgumentException>(() => Context.Cars.PaginateAsync(1, -1));
43+
}
44+
[Order(1)]
45+
[TestCase(Category = ASYNC_TESTS)]
46+
public void AsyncPg_CancelledToken_Throw()
47+
{
48+
var src = new CancellationTokenSource();
49+
src.Cancel();
50+
Assert.ThrowsAsync<OperationCanceledException>(() => Context.Cars.PaginateAsync(1, 2, cancellationToken: src.Token));
51+
}
52+
[Order(2)]
53+
[TestCase(Category = ASYNC_TESTS)]
54+
public async Task AsyncPg_Skip_Count()
55+
{
56+
Add(new Car());
57+
Add(new Car());
58+
Add(new Car());
59+
Add(new Car());
60+
61+
var paged = await Context.Cars.PaginateAsync(1, 2, true);
62+
63+
Assert.IsNotNull(paged);
64+
Assert.AreEqual(2, paged.TotalItems);
65+
Assert.AreEqual(1, paged.TotalPages);
66+
67+
Log(paged);
68+
}
69+
[Order(2)]
70+
[TestCase(Category = ASYNC_TESTS)]
71+
public async Task AsyncPg_With_Items()
72+
{
73+
int k = 5174;
74+
for (int i = 0; i < k; i++)
75+
Context.Add(new Car());
76+
77+
Save();
78+
79+
var paged = await Context.Cars.PaginateAsync(35, 8);
80+
81+
Assert.IsNotNull(paged);
82+
Assert.AreEqual(k, paged.TotalItems);
83+
Assert.AreEqual(647, paged.TotalPages);
84+
85+
Log(paged);
86+
}
87+
#endregion
88+
}
89+
}

tests/SynchronousTests.cs

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Paginator.EntityFrameworkCore;
2+
using NUnit.Framework;
3+
using System;
4+
5+
namespace tests
6+
{
7+
[TestFixture]
8+
//[SingleThreaded]
9+
internal class SynchronousTests : TestBaseContext
10+
{
11+
[SetUp]
12+
public void Setup()
13+
{
14+
InitContx();
15+
}
16+
17+
#region Synchronous
18+
[Order(0)]
19+
[TestCase(Category = SYNC_TESTS)]
20+
public void Pg_Empty()
21+
{
22+
var paged = Context.Cars.Paginate(1, 10);
23+
24+
Assert.IsNotNull(paged);
25+
Assert.Zero(paged.TotalItems);
26+
27+
Log(paged);
28+
}
29+
[Order(1)]
30+
[TestCase(Category = SYNC_TESTS)]
31+
public void Pg_Invalid_PageParam_ThrowEx()
32+
{
33+
Assert.Throws<ArgumentException>(() => Context.Cars.Paginate(0, 2));
34+
Assert.Throws<ArgumentException>(() => Context.Cars.Paginate(-1, 2));
35+
}
36+
[Order(1)]
37+
[TestCase(Category = SYNC_TESTS)]
38+
public void Pg_Invalid_PerPageParam_ThrowEx()
39+
{
40+
Assert.Throws<ArgumentException>(() => Context.Cars.Paginate(1, -1));
41+
}
42+
[Order(2)]
43+
[TestCase(Category = SYNC_TESTS)]
44+
public void Pg_Skip_Count()
45+
{
46+
Add(new Car());
47+
Add(new Car());
48+
Add(new Car());
49+
Add(new Car());
50+
51+
var paged = Context.Cars.Paginate(1, 2, true);
52+
53+
Assert.IsNotNull(paged);
54+
Assert.AreEqual(2, paged.TotalItems);
55+
Assert.AreEqual(1, paged.TotalPages);
56+
57+
Log(paged);
58+
}
59+
[Order(2)]
60+
[RequiresThread]
61+
[TestCase(Category = SYNC_TESTS)]
62+
public void Pg_With_Items()
63+
{
64+
int k = 5174;
65+
for (int i = 0; i < k; i++)
66+
Context.Add(new Car());
67+
68+
Save();
69+
70+
var paged = Context.Cars.Paginate(35, 8);
71+
72+
Assert.IsNotNull(paged);
73+
Assert.AreEqual(k, paged.TotalItems);
74+
Assert.AreEqual(647, paged.TotalPages);
75+
76+
Log(paged);
77+
}
78+
#endregion
79+
}
80+
}

tests/TestBase.cs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
3+
namespace tests
4+
{
5+
internal class TestBase
6+
{
7+
internal const string SYNC_TESTS = "Synchronous";
8+
internal const string ASYNC_TESTS = "Asynchronous";
9+
internal const string SERIAL_TESTS = "Serialization";
10+
11+
internal void Log(object obj)
12+
=> Console.WriteLine(obj);
13+
internal void Log(string format, params object[] args)
14+
=> Console.WriteLine(format, args);
15+
}
16+
}

tests/TestBaseContext.cs

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
11
using Microsoft.EntityFrameworkCore;
2+
using NUnit.Framework;
23

34
namespace tests
45
{
5-
internal class TestBaseContext
6+
internal class TestBaseContext : TestBase
67
{
78
private TestDbContext _context;
89
private DbContextOptions<TestDbContext> DbContextOptions;
910

1011
public TestBaseContext()
1112
{ }
12-
13+
1314
protected TestDbContext Context
1415
{
1516
get
1617
{
1718
if (_context == null)
18-
{
19-
DbContextOptions = new DbContextOptionsBuilder<TestDbContext>()
19+
InitContx();
20+
return _context;
21+
}
22+
}
23+
protected void InitContx()
24+
{
25+
DbContextOptions = new DbContextOptionsBuilder<TestDbContext>()
2026
.UseInMemoryDatabase(databaseName: "test_db")
2127
.EnableServiceProviderCaching(true)
2228
.EnableSensitiveDataLogging(true)
2329
.UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll)
2430
.Options;
25-
_context = new TestDbContext(DbContextOptions);
26-
}
27-
return _context;
28-
}
31+
_context = new TestDbContext(DbContextOptions);
2932
}
3033

3134

tests/TestDbContext.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public TestDbContext(DbContextOptions<TestDbContext> options)
1010

1111
public DbSet<Car> Cars { get; set; }
1212
}
13-
internal class Car
13+
public class Car
1414
{
1515
public int Id { get; set; }
1616
}

0 commit comments

Comments
 (0)