Skip to content

Commit

Permalink
Refactored IQuery Api to handle paging, paging implementation, now re…
Browse files Browse the repository at this point in the history
…moved from URF.
  • Loading branch information
lelong37 committed Feb 9, 2018
1 parent 2342f45 commit b15b206
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 247 deletions.
22 changes: 22 additions & 0 deletions URF.Core.Abstractions/IQuery.cs
Original file line number Diff line number Diff line change
@@ -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<TEntity> where TEntity : class
{
IQuery<TEntity> Where(Expression<Func<TEntity, bool>> filter);
IQuery<TEntity> Include(Expression<Func<TEntity, object>> include);
IQuery<TEntity> OrderBy(Expression<Func<TEntity, object>> sortBy);
IQuery<TEntity> OrderByDescending(Expression<Func<TEntity, object>> sortBy);
Task<IEnumerable<TEntity>> SelectAsync(CancellationToken cancellationToken = default);
IQuery<TEntity> Skip(int skip);
IQuery<TEntity> Take(int take);
IQuery<TEntity> ThenBy(Expression<Func<TEntity, object>> sortBy);
IQuery<TEntity> ThenByDescending(Expression<Func<TEntity, object>> sortBy);
Task<int> CountAsync(CancellationToken cancellationToken = default);
}
}
18 changes: 7 additions & 11 deletions URF.Core.Abstractions/IRepository.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
using System;
#region

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using URF.Core.Abstractions;

#endregion

namespace Urf.Core.Abstractions
{
public interface IRepository<TEntity> where TEntity : class
{
Task<IEnumerable<TEntity>> SelectAsync(CancellationToken cancellationToken = default);
Task<IEnumerable<TEntity>> SelectSqlAsync(string sql, object[] parameters, CancellationToken cancellationToken = default);
Task<TEntity> FindAsync(object[] keyValues, CancellationToken cancellationToken = default);
Task<TEntity> FindAsync<TKey>(TKey keyValue, CancellationToken cancellationToken = default);
Expand All @@ -26,13 +29,6 @@ public interface IRepository<TEntity> where TEntity : class
Task<bool> DeleteAsync<TKey>(TKey keyValue, CancellationToken cancellationToken = default);
IQueryable<TEntity> Queryable();
IQueryable<TEntity> QueryableSql(string sql, params object[] parameters);
Task<IEnumerable<TEntity>> SelectAsync(
Expression<Func<TEntity, bool>> filter = null,
Expression<Func<TEntity, object>>[] includes = null,
ISortExpression<TEntity>[] sortExpressions = null,
int? page = null,
int? pageSize = null,
CancellationToken cancellationToken = default);
IRepositoryFluent<TEntity> Query();
IQuery<TEntity> Query();
}
}
}
19 changes: 0 additions & 19 deletions URF.Core.Abstractions/IRepositoryFluent.cs

This file was deleted.

12 changes: 0 additions & 12 deletions URF.Core.Abstractions/ISortExpression.cs

This file was deleted.

15 changes: 15 additions & 0 deletions URF.Core.EF.Tests/Models/Page.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Collections.Generic;

namespace URF.Core.EF.Tests.Models
{
class Page<TEntity>
{
public Page(IEnumerable<TEntity> value, int count)
{
Value = value;
Count = count;
}
public IEnumerable<TEntity> Value { get; set; }
public int Count { get; set; }
}
}
82 changes: 26 additions & 56 deletions URF.Core.EF.Tests/RepositoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Product>(_fixture.Context);

Expand All @@ -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<Func<Product, object>>[] { p => p.Category },
sortExpressions: new ISortExpression<Product>[] { new SortExpression<Product>(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<Product>(_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<Product>[] 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),
Expand All @@ -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<Product>(enumerable, count);

Assert.Equal(assertionCount, paginated.Count);
Assert.Collection(paginated.Value, collectionAssertions);
}

[Fact]
Expand Down Expand Up @@ -389,7 +359,7 @@ public async Task SelectAsync_Should_Return_Entities()
var repository = new Repository<Product>(_fixture.Context);

// Act
var products = await repository.SelectAsync(default);
var products = await repository.SelectAsync();
var enumerable = products as Product[] ?? products.ToArray();

// Assert
Expand Down
10 changes: 6 additions & 4 deletions URF.Core.EF.Trackable/URF.Core.EF.Trackable.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<Compile Remove="Models\**" />
<EmbeddedResource Remove="Models\**" />
<None Remove="Models\**" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.1" />
Expand All @@ -18,8 +24,4 @@
<ProjectReference Include="..\URF.Core.EF\URF.Core.EF.csproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="Models\" />
</ItemGroup>

</Project>
18 changes: 18 additions & 0 deletions URF.Core.EF/Page.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
using URF.Core.Abstractions;

namespace URF.Core.EF
{
class Page<TEntity>
{
public Page(IEnumerable<TEntity> value, int count)
{
Value = value;
Count = count;
}
public IEnumerable<TEntity> Value { get; set; }
public int Count { get; set; }
}
}
92 changes: 92 additions & 0 deletions URF.Core.EF/Query.cs
Original file line number Diff line number Diff line change
@@ -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<TEntity> : IQuery<TEntity> where TEntity : class
{
private int? _skip;
private int? _take;
private IQueryable<TEntity> _queryable;
private IOrderedQueryable<TEntity> _orderedQuery;

public Query(IRepository<TEntity> repository)
{
_queryable = repository.Queryable();
}

public IQuery<TEntity> Where(Expression<Func<TEntity, bool>> filter)
{
_queryable =_queryable.Where(filter);
return this;
}

public IQuery<TEntity> Include(Expression<Func<TEntity, object>> include)
{
_queryable =_queryable.Include(include);
return this;
}

public IQuery<TEntity> OrderBy(Expression<Func<TEntity, object>> sortBy)
{
if (_orderedQuery == null) _orderedQuery = _queryable.OrderBy(sortBy);
else _orderedQuery.OrderBy(sortBy);
return this;
}

public IQuery<TEntity> ThenBy(Expression<Func<TEntity, object>> sortBy)
{
_orderedQuery.ThenBy(sortBy);
return this;
}

public IQuery<TEntity> OrderByDescending(Expression<Func<TEntity, object>> sortBy)
{
if (_orderedQuery == null) _orderedQuery = _queryable.OrderByDescending(sortBy);
else _orderedQuery.OrderByDescending(sortBy);
return this;
}
public IQuery<TEntity> ThenByDescending(Expression<Func<TEntity, object>> sortBy)
{
_orderedQuery.ThenByDescending(sortBy);
return this;
}

public async Task<int> CountAsync(CancellationToken cancellationToken = default )
{
return await _queryable.CountAsync(cancellationToken);
}

public IQuery<TEntity> Skip(int skip)
{
_skip = skip;
return this;
}

public IQuery<TEntity> Take(int take)
{
_take = take;
return this;
}

public virtual async Task<IEnumerable<TEntity>> 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);
}
}
}
Loading

0 comments on commit b15b206

Please sign in to comment.