From b15b2060f74179b60c27a9a158851ac269dbaad7 Mon Sep 17 00:00:00 2001 From: Long Le Date: Thu, 8 Feb 2018 22:52:53 -0600 Subject: [PATCH] Refactored IQuery Api to handle paging, paging implementation, now removed from URF. --- URF.Core.Abstractions/IQuery.cs | 22 +++++ URF.Core.Abstractions/IRepository.cs | 18 ++-- URF.Core.Abstractions/IRepositoryFluent.cs | 19 ---- URF.Core.Abstractions/ISortExpression.cs | 12 --- URF.Core.EF.Tests/Models/Page.cs | 15 +++ URF.Core.EF.Tests/RepositoryTest.cs | 82 ++++++----------- .../URF.Core.EF.Trackable.csproj | 10 +- URF.Core.EF/Page.cs | 18 ++++ URF.Core.EF/Query.cs | 92 +++++++++++++++++++ URF.Core.EF/Repository.cs | 54 ++--------- URF.Core.EF/RepositoryFluent.cs | 78 ---------------- URF.Core.EF/SortExpression.cs | 19 ---- 12 files changed, 192 insertions(+), 247 deletions(-) create mode 100644 URF.Core.Abstractions/IQuery.cs delete mode 100644 URF.Core.Abstractions/IRepositoryFluent.cs delete mode 100644 URF.Core.Abstractions/ISortExpression.cs create mode 100644 URF.Core.EF.Tests/Models/Page.cs create mode 100644 URF.Core.EF/Page.cs create mode 100644 URF.Core.EF/Query.cs delete mode 100644 URF.Core.EF/RepositoryFluent.cs delete mode 100644 URF.Core.EF/SortExpression.cs diff --git a/URF.Core.Abstractions/IQuery.cs b/URF.Core.Abstractions/IQuery.cs new file mode 100644 index 0000000..8dde96d --- /dev/null +++ b/URF.Core.Abstractions/IQuery.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace URF.Core.Abstractions +{ + public interface IQuery where TEntity : class + { + IQuery Where(Expression> filter); + IQuery Include(Expression> include); + IQuery OrderBy(Expression> sortBy); + IQuery OrderByDescending(Expression> sortBy); + Task> SelectAsync(CancellationToken cancellationToken = default); + IQuery Skip(int skip); + IQuery Take(int take); + IQuery ThenBy(Expression> sortBy); + IQuery ThenByDescending(Expression> sortBy); + Task CountAsync(CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/URF.Core.Abstractions/IRepository.cs b/URF.Core.Abstractions/IRepository.cs index f76c2aa..e214e28 100644 --- a/URF.Core.Abstractions/IRepository.cs +++ b/URF.Core.Abstractions/IRepository.cs @@ -1,4 +1,6 @@ -using System; +#region + +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -6,11 +8,12 @@ using System.Threading.Tasks; using URF.Core.Abstractions; +#endregion + namespace Urf.Core.Abstractions { public interface IRepository where TEntity : class { - Task> SelectAsync(CancellationToken cancellationToken = default); Task> SelectSqlAsync(string sql, object[] parameters, CancellationToken cancellationToken = default); Task FindAsync(object[] keyValues, CancellationToken cancellationToken = default); Task FindAsync(TKey keyValue, CancellationToken cancellationToken = default); @@ -26,13 +29,6 @@ public interface IRepository where TEntity : class Task DeleteAsync(TKey keyValue, CancellationToken cancellationToken = default); IQueryable Queryable(); IQueryable QueryableSql(string sql, params object[] parameters); - Task> SelectAsync( - Expression> filter = null, - Expression>[] includes = null, - ISortExpression[] sortExpressions = null, - int? page = null, - int? pageSize = null, - CancellationToken cancellationToken = default); - IRepositoryFluent Query(); + IQuery Query(); } -} +} \ No newline at end of file diff --git a/URF.Core.Abstractions/IRepositoryFluent.cs b/URF.Core.Abstractions/IRepositoryFluent.cs deleted file mode 100644 index 0740b1c..0000000 --- a/URF.Core.Abstractions/IRepositoryFluent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Threading; -using System.Threading.Tasks; - -namespace URF.Core.Abstractions -{ - public interface IRepositoryFluent where TEntity : class - { - IRepositoryFluent Filter(Expression> filter); - IRepositoryFluent Include(Expression> include); - IRepositoryFluent Page(int page); - IRepositoryFluent PageSize(int pageSize); - Task> SelectAsync(CancellationToken cancellationToken = default ); - IRepositoryFluent OrderBy(Expression> sortBy); - IRepositoryFluent OrderByDescending(Expression> sortBy); - } -} \ No newline at end of file diff --git a/URF.Core.Abstractions/ISortExpression.cs b/URF.Core.Abstractions/ISortExpression.cs deleted file mode 100644 index 478cfa7..0000000 --- a/URF.Core.Abstractions/ISortExpression.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq.Expressions; - -namespace URF.Core.Abstractions -{ - public interface ISortExpression - { - Expression> SortBy { get; set; } - ListSortDirection SortDirection { get; set; } - } -} \ No newline at end of file diff --git a/URF.Core.EF.Tests/Models/Page.cs b/URF.Core.EF.Tests/Models/Page.cs new file mode 100644 index 0000000..f5f4fe1 --- /dev/null +++ b/URF.Core.EF.Tests/Models/Page.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace URF.Core.EF.Tests.Models +{ + class Page + { + public Page(IEnumerable value, int count) + { + Value = value; + Count = count; + } + public IEnumerable Value { get; set; } + public int Count { get; set; } + } +} diff --git a/URF.Core.EF.Tests/RepositoryTest.cs b/URF.Core.EF.Tests/RepositoryTest.cs index c830ac2..2c3a736 100644 --- a/URF.Core.EF.Tests/RepositoryTest.cs +++ b/URF.Core.EF.Tests/RepositoryTest.cs @@ -255,10 +255,10 @@ public async Task LoadPropertyAsync_Should_Load_Property() // Assert Assert.Same(_categories[0], product.Category); - } + } [Fact] - public async Task Paging_Should_Return_Page() + public async Task Fluent_Api_Should_Support_Paging() { var repository = new Repository(_fixture.Context); @@ -276,68 +276,28 @@ public async Task Paging_Should_Return_Page() new Product {ProductId = 4, ProductName = "Chef Anton's Cajun Seasoning", CategoryId = 2, UnitPrice = 22.00m, Discontinued = false} }; - var products = await repository - .SelectAsync( - filter: p => p.CategoryId == 1 || p.CategoryId == 2, - includes: new Expression>[] { p => p.Category }, - sortExpressions: new ISortExpression[] { new SortExpression(p => p.ProductName, ListSortDirection.Descending) }, - page: 2, - pageSize:10, - cancellationToken: default); - - var enumerable = products as Product[] ?? products.ToArray(); - - Assert.NotEmpty(enumerable); - Assert.Equal(10, enumerable.Count()); - - Assert.Collection(enumerable, - p => Assert.Equal(expected[0].ProductId, p.ProductId), - p => Assert.Equal(expected[1].ProductId, p.ProductId), - p => Assert.Equal(expected[2].ProductId, p.ProductId), - p => Assert.Equal(expected[3].ProductId, p.ProductId), - p => Assert.Equal(expected[4].ProductId, p.ProductId), - p => Assert.Equal(expected[5].ProductId, p.ProductId), - p => Assert.Equal(expected[6].ProductId, p.ProductId), - p => Assert.Equal(expected[7].ProductId, p.ProductId), - p => Assert.Equal(expected[8].ProductId, p.ProductId), - p => Assert.Equal(expected[9].ProductId, p.ProductId) - ); - } - - [Fact] - public async Task Paging_Fluent_Should_Return_Page() - { - var repository = new Repository(_fixture.Context); + const int page = 2; // current page + const int pageSize = 10; // page size - var expected = new[] - { - new Product {ProductId = 67, ProductName = "Laughing Lumberjack Lager", CategoryId = 1, UnitPrice = 14.00m, Discontinued = false}, - new Product {ProductId = 76, ProductName = "Lakkalikööri", CategoryId = 1, UnitPrice = 18.00m, Discontinued = false}, - new Product {ProductId = 43, ProductName = "Ipoh Coffee", CategoryId = 1, UnitPrice = 46.00m, Discontinued = false}, - new Product {ProductId = 44, ProductName = "Gula Malacca", CategoryId = 2, UnitPrice = 19.45m, Discontinued = false}, - new Product {ProductId = 24, ProductName = "Guaraná Fantástica", CategoryId = 1, UnitPrice = 4.50m, Discontinued = true}, - new Product {ProductId = 6, ProductName = "Grandma's Boysenberry Spread", CategoryId = 2, UnitPrice = 25.00m, Discontinued = false}, - new Product {ProductId = 15, ProductName = "Genen Shouyu", CategoryId = 2, UnitPrice = 15.50m, Discontinued = false}, - new Product {ProductId = 38, ProductName = "Côte de Blaye", CategoryId = 1, UnitPrice = 263.50m, Discontinued = false}, - new Product {ProductId = 5, ProductName = "Chef Anton's Gumbo Mix", CategoryId = 2, UnitPrice = 21.35m, Discontinued = true}, - new Product {ProductId = 4, ProductName = "Chef Anton's Cajun Seasoning", CategoryId = 2, UnitPrice = 22.00m, Discontinued = false} - }; + var count = await repository // total count is needed for paging + .Query() + .Where(p => p.CategoryId == 1 || p.CategoryId == 2) + .CountAsync(); - var products = await repository + var products = await repository // paging w/ filter, deep loading, sorting .Query() - .Filter(p => p.CategoryId == 1 || p.CategoryId == 2) + .Where(p => p.CategoryId == 1 || p.CategoryId == 2) .Include(p => p.Category) .OrderByDescending(p => p.ProductName) - .Page(2) - .PageSize(10) + .Skip((page - 1) * pageSize) + .Take(pageSize) .SelectAsync(); var enumerable = products as Product[] ?? products.ToArray(); - Assert.NotEmpty(enumerable); - Assert.Equal(10, enumerable.Count()); + const int assertionCount = 24; - Assert.Collection(enumerable, + Action[] collectionAssertions = { p => Assert.Equal(expected[0].ProductId, p.ProductId), p => Assert.Equal(expected[1].ProductId, p.ProductId), p => Assert.Equal(expected[2].ProductId, p.ProductId), @@ -348,7 +308,17 @@ public async Task Paging_Fluent_Should_Return_Page() p => Assert.Equal(expected[7].ProductId, p.ProductId), p => Assert.Equal(expected[8].ProductId, p.ProductId), p => Assert.Equal(expected[9].ProductId, p.ProductId) - ); + }; + + Assert.NotEmpty(enumerable); + Assert.Equal(assertionCount, count); + Assert.Equal(pageSize, enumerable.Length); + Assert.Collection(enumerable, collectionAssertions); + + var paginated = new Page(enumerable, count); + + Assert.Equal(assertionCount, paginated.Count); + Assert.Collection(paginated.Value, collectionAssertions); } [Fact] @@ -389,7 +359,7 @@ public async Task SelectAsync_Should_Return_Entities() var repository = new Repository(_fixture.Context); // Act - var products = await repository.SelectAsync(default); + var products = await repository.SelectAsync(); var enumerable = products as Product[] ?? products.ToArray(); // Assert diff --git a/URF.Core.EF.Trackable/URF.Core.EF.Trackable.csproj b/URF.Core.EF.Trackable/URF.Core.EF.Trackable.csproj index 0f46db3..a7b0fc9 100644 --- a/URF.Core.EF.Trackable/URF.Core.EF.Trackable.csproj +++ b/URF.Core.EF.Trackable/URF.Core.EF.Trackable.csproj @@ -5,6 +5,12 @@ latest + + + + + + @@ -18,8 +24,4 @@ - - - - diff --git a/URF.Core.EF/Page.cs b/URF.Core.EF/Page.cs new file mode 100644 index 0000000..9ec6b57 --- /dev/null +++ b/URF.Core.EF/Page.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; +using URF.Core.Abstractions; + +namespace URF.Core.EF +{ + class Page + { + public Page(IEnumerable value, int count) + { + Value = value; + Count = count; + } + public IEnumerable Value { get; set; } + public int Count { get; set; } + } +} diff --git a/URF.Core.EF/Query.cs b/URF.Core.EF/Query.cs new file mode 100644 index 0000000..ea8e84b --- /dev/null +++ b/URF.Core.EF/Query.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Urf.Core.Abstractions; +using URF.Core.Abstractions; + +namespace URF.Core.EF +{ + class Query : IQuery where TEntity : class + { + private int? _skip; + private int? _take; + private IQueryable _queryable; + private IOrderedQueryable _orderedQuery; + + public Query(IRepository repository) + { + _queryable = repository.Queryable(); + } + + public IQuery Where(Expression> filter) + { + _queryable =_queryable.Where(filter); + return this; + } + + public IQuery Include(Expression> include) + { + _queryable =_queryable.Include(include); + return this; + } + + public IQuery OrderBy(Expression> sortBy) + { + if (_orderedQuery == null) _orderedQuery = _queryable.OrderBy(sortBy); + else _orderedQuery.OrderBy(sortBy); + return this; + } + + public IQuery ThenBy(Expression> sortBy) + { + _orderedQuery.ThenBy(sortBy); + return this; + } + + public IQuery OrderByDescending(Expression> sortBy) + { + if (_orderedQuery == null) _orderedQuery = _queryable.OrderByDescending(sortBy); + else _orderedQuery.OrderByDescending(sortBy); + return this; + } + public IQuery ThenByDescending(Expression> sortBy) + { + _orderedQuery.ThenByDescending(sortBy); + return this; + } + + public async Task CountAsync(CancellationToken cancellationToken = default ) + { + return await _queryable.CountAsync(cancellationToken); + } + + public IQuery Skip(int skip) + { + _skip = skip; + return this; + } + + public IQuery Take(int take) + { + _take = take; + return this; + } + + public virtual async Task> SelectAsync(CancellationToken cancellationToken = default ) + { + _queryable = _orderedQuery ?? _queryable; + + if(_skip.HasValue) _queryable = _queryable.Skip(_skip.Value); + if (_take.HasValue) _queryable = _queryable.Take(_take.Value); + + return await _queryable.ToListAsync(cancellationToken); + } + } +} diff --git a/URF.Core.EF/Repository.cs b/URF.Core.EF/Repository.cs index 04e9ec7..9b86dc0 100644 --- a/URF.Core.EF/Repository.cs +++ b/URF.Core.EF/Repository.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Threading; @@ -15,18 +14,15 @@ public class Repository : IRepository where TEntity : class { protected DbContext Context { get; } protected DbSet Set { get; } - private readonly RepositoryFluent _repositoryFluent; + private readonly Query _query; public Repository(DbContext context) { Context = context; Set = context.Set(); - _repositoryFluent = new RepositoryFluent(this); + _query = new Query(this); } - public virtual async Task> SelectAsync(CancellationToken cancellationToken = default) - => await SelectAsync(null, null, null, null, null, cancellationToken); - public virtual async Task> SelectSqlAsync(string sql, object[] parameters, CancellationToken cancellationToken = default) => await Set.FromSql(sql, (object[])parameters).ToListAsync(cancellationToken); @@ -77,49 +73,11 @@ public virtual async Task DeleteAsync(TKey keyValue, CancellationTok public virtual IQueryable Queryable() => Set; public virtual IQueryable QueryableSql(string sql, params object[] parameters) - => Set.FromSql(sql, parameters); - - public virtual async Task> SelectAsync( - Expression> filter = null, - Expression>[] includes = null, - ISortExpression[] sortExpressions = null, - int? page = null, - int? pageSize = null, - CancellationToken cancellationToken = default) - { - IQueryable query = Set; - - if (filter != null) - query = query.Where(filter); - - if (includes != null) - foreach (var include in includes) - query.Include(include); - - if (sortExpressions != null) - { - IOrderedQueryable orderedQuery = null; - - for (var i = 0; i < sortExpressions.Count(); i++) - orderedQuery = sortExpressions[i].SortDirection == ListSortDirection.Ascending - ? (i == 0 ? query.OrderBy(sortExpressions[i].SortBy) - : orderedQuery.ThenBy(sortExpressions[i].SortBy)) - : (i == 0 ? query.OrderByDescending(sortExpressions[i].SortBy) - : orderedQuery.ThenByDescending(sortExpressions[i].SortBy)); + => Set.FromSql(sql, parameters); - if (pageSize.HasValue && page.HasValue) - query = orderedQuery.Skip((page.Value - 1) * pageSize.Value); - } + public async Task> SelectAsync(CancellationToken cancellationToken = default) + => await Set.ToListAsync(cancellationToken); - if (pageSize.HasValue) - query = query.Take(pageSize.Value); - - return await query.ToListAsync(cancellationToken); - } - - public virtual IRepositoryFluent Query() - { - return _repositoryFluent; - } + public virtual IQuery Query() =>_query; } } diff --git a/URF.Core.EF/RepositoryFluent.cs b/URF.Core.EF/RepositoryFluent.cs deleted file mode 100644 index df37440..0000000 --- a/URF.Core.EF/RepositoryFluent.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq.Expressions; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Urf.Core.Abstractions; -using URF.Core.Abstractions; - -namespace URF.Core.EF -{ - class RepositoryFluent : IRepositoryFluent where TEntity : class - { - private readonly IRepository _repository; - private readonly List>> _includes; - private readonly List> _sorts; - private Expression> _filter; - private int? _page; - private int? _pageSize; - - public RepositoryFluent(IRepository repository) - { - _repository = repository; - _filter = null; - _includes = new List>>(); - _sorts = new List>(); - } - - public IRepositoryFluent Filter(Expression> filter) - { - _filter = filter; - return this; - } - - public IRepositoryFluent Include(Expression> include) - { - _includes.Add(include); - return this; - } - - public IRepositoryFluent OrderBy(Expression> sortBy) - { - _sorts.Add(new SortExpression(sortBy, ListSortDirection.Ascending)); - return this; - } - - public IRepositoryFluent OrderByDescending(Expression> sortBy) - { - _sorts.Add(new SortExpression(sortBy, ListSortDirection.Descending)); - return this; - } - - public IRepositoryFluent Page(int page) - { - _page = page; - return this; - } - - public IRepositoryFluent PageSize(int pageSize) - { - _pageSize = pageSize; - return this; - } - - public virtual async Task> SelectAsync(CancellationToken cancellationToken = default ) - { - return await _repository - .SelectAsync( - filter: _filter, - includes: _includes.ToArray(), - sortExpressions: _sorts.ToArray(), - page: 2, - pageSize: 10, - cancellationToken: cancellationToken); - } - } -} diff --git a/URF.Core.EF/SortExpression.cs b/URF.Core.EF/SortExpression.cs deleted file mode 100644 index 5742e14..0000000 --- a/URF.Core.EF/SortExpression.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.ComponentModel; -using System.Linq.Expressions; -using URF.Core.Abstractions; - -namespace URF.Core.EF -{ - public class SortExpression : ISortExpression - { - public SortExpression(Expression> sortBy, ListSortDirection sortDirection) - { - SortBy = sortBy; - SortDirection = sortDirection; - } - - public Expression> SortBy { get; set; } - public ListSortDirection SortDirection { get; set; } - } -} \ No newline at end of file