Skip to content

Commit

Permalink
PaginateViaPrimaryKey option (#346)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlekseyMartynov authored Mar 27, 2019
1 parent 272745a commit 5550aad
Show file tree
Hide file tree
Showing 15 changed files with 358 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace DevExtreme.AspNet.Data.Tests {

public class PaginateViaPrimaryKeyTestHelper {

public interface IDataItem {
int K1 { get; set; }
long K2 { get; set; }
}

public static IEnumerable<T> CreateTestData<T>() where T : IDataItem, new() {
for(var i = 1; i <= 3; i++)
yield return new T { K1 = i, K2 = i };
}

public static void Run<T>(IQueryable<T> source) {
Run(source, new[] { "K1" });
Run(source, new[] { "K1", "K2" });
}

static void Run<T>(IQueryable<T> source, string[] pk) {
var loadOptions = new SampleLoadOptions {
PrimaryKey = pk,
PaginateViaPrimaryKey = true,
Filter = new[] { "K1", ">", "1" },
Select = new[] { "K1", "K2" },
Sort = new[] {
new SortingInfo { Selector = "K1" }
},
Skip = 1,
Take = 1,
RequireTotalCount = true
};

var loadResult = DataSourceLoader.Load(source, loadOptions);
var data = loadResult.data.Cast<IDictionary<string, object>>().ToArray();

Assert.Single(data);
Assert.Equal(3, data[0]["K1"]);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
<Compile Include="Bug235.cs" />
<Compile Include="Bug239.cs" />
<Compile Include="Bug240.cs" />
<Compile Include="PaginateViaPrimaryKey.cs" />
<Compile Include="RemoteGroupingStress.cs" />
<Compile Include="Summary.cs" />
<Compile Include="SelectNotMapped.cs" />
Expand Down
30 changes: 30 additions & 0 deletions net/DevExtreme.AspNet.Data.Tests.EF6/PaginateViaPrimaryKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Xunit;

namespace DevExtreme.AspNet.Data.Tests.EF6 {

public class PaginateViaPrimaryKey_DataItem : PaginateViaPrimaryKeyTestHelper.IDataItem {
public int K1 { get; set; }
public long K2 { get; set; }
}

public class PaginateViaPrimaryKey {

[Fact]
public void Scenario() {
TestDbContext.Exec(context => {
var set = context.Set<PaginateViaPrimaryKey_DataItem>();
foreach(var i in PaginateViaPrimaryKeyTestHelper.CreateTestData<PaginateViaPrimaryKey_DataItem>())
set.Add(i);
context.SaveChanges();
PaginateViaPrimaryKeyTestHelper.Run(set);
PaginateViaPrimaryKeyTestHelper.Run(set.Select(i => new { i.K1, i.K2 }));
});
}

}

}
1 change: 1 addition & 0 deletions net/DevExtreme.AspNet.Data.Tests.EF6/TestDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<SelectNotMapped_DataItem>();
modelBuilder.Entity<RemoteGroupingStress_DataItem>();
modelBuilder.Entity<Summary_DataItem>();
modelBuilder.Entity<PaginateViaPrimaryKey_DataItem>().HasKey(i => new { i.K1, i.K2 });
}

public static void Exec(Action<TestDbContext> action) {
Expand Down
30 changes: 30 additions & 0 deletions net/DevExtreme.AspNet.Data.Tests.EFCore2/PaginateViaPrimaryKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Xunit;

namespace DevExtreme.AspNet.Data.Tests.EFCore2 {

public class PaginateViaPrimaryKey {

[Table(nameof(PaginateViaPrimaryKey) + "_" + nameof(DataItem))]
public class DataItem : PaginateViaPrimaryKeyTestHelper.IDataItem {
public int K1 { get; set; }
public long K2 { get; set; }
}

[Fact]
public void Scenario() {
TestDbContext.Exec(context => {
var set = context.Set<DataItem>();
set.AddRange(PaginateViaPrimaryKeyTestHelper.CreateTestData<DataItem>());
context.SaveChanges();
PaginateViaPrimaryKeyTestHelper.Run(set);
PaginateViaPrimaryKeyTestHelper.Run(set.Select(i => new { i.K1, i.K2 }));
});
}

}

}
1 change: 1 addition & 0 deletions net/DevExtreme.AspNet.Data.Tests.EFCore2/TestDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<RemoteGroupingStress.DataItem>();
modelBuilder.Entity<Summary.DataItem>();
modelBuilder.Entity<Bug326.Entity>();
modelBuilder.Entity<PaginateViaPrimaryKey.DataItem>().HasKey("K1", "K2");
}

public static void Exec(Action<TestDbContext> action) {
Expand Down
37 changes: 37 additions & 0 deletions net/DevExtreme.AspNet.Data.Tests.NH/PaginateViaPrimaryKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using FluentNHibernate.Mapping;
using System;
using System.Linq;
using Xunit;

namespace DevExtreme.AspNet.Data.Tests.NH {

public class PaginateViaPrimaryKey {

public class DataItem : PaginateViaPrimaryKeyTestHelper.IDataItem {
public virtual int K1 { get; set; }
public virtual long K2 { get; set; }
}

public class DataItemMap : ClassMap<DataItem> {
public DataItemMap() {
Table(nameof(PaginateViaPrimaryKey) + "_" + nameof(DataItem));
Id(p => p.K1);
Map(p => p.K2);
}
}

[Fact]
public void Scenario() {
SessionFactoryHelper.Exec(session => {
foreach(var i in PaginateViaPrimaryKeyTestHelper.CreateTestData<DataItem>())
session.Save(i);
var query = session.Query<DataItem>();
PaginateViaPrimaryKeyTestHelper.Run(query);
PaginateViaPrimaryKeyTestHelper.Run(query.Select(i => new { i.K1, i.K2 }));
});
}

}

}
31 changes: 31 additions & 0 deletions net/DevExtreme.AspNet.Data.Tests.Xpo/PaginateViaPrimaryKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using DevExpress.Xpo;
using System;
using System.Linq;
using Xunit;

namespace DevExtreme.AspNet.Data.Tests.Xpo {

public class PaginateViaPrimaryKey {

[Persistent(nameof(PaginateViaPrimaryKey) + "_" + nameof(DataItem))]
public class DataItem : PaginateViaPrimaryKeyTestHelper.IDataItem {
[Key]
public int K1 { get; set; }
public long K2 { get; set; }
}

[Fact]
public void Scenario() {
UnitOfWorkHelper.Exec(uow => {
foreach(var i in PaginateViaPrimaryKeyTestHelper.CreateTestData<DataItem>())
uow.Save(i);
uow.CommitChanges();
var query = uow.Query<DataItem>();
PaginateViaPrimaryKeyTestHelper.Run(query);
PaginateViaPrimaryKeyTestHelper.Run(query.Select(i => new { i.K1, i.K2 }));
});
}
}

}
3 changes: 2 additions & 1 deletion net/DevExtreme.AspNet.Data.Tests.Xpo/UnitOfWorkHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public static void Exec(Action<UnitOfWork> action) {
typeof(DefaultSort.DataItem),
typeof(RemoteGroupingStress.DataItem),
typeof(Summary.DataItem),
typeof(Bug339.DataItem)
typeof(Bug339.DataItem),
typeof(PaginateViaPrimaryKey.DataItem)
);

var provider = XpoDefault.GetConnectionProvider(
Expand Down
108 changes: 108 additions & 0 deletions net/DevExtreme.AspNet.Data.Tests/PaginateViaPrimaryKeyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using Newtonsoft.Json;
using System;
using System.Linq;
using Xunit;

namespace DevExtreme.AspNet.Data.Tests {

public class PaginateViaPrimaryKeyTests {

[Fact]
public void SingleKey() {
var data = Enumerable.Range(0, 5)
.Select(i => new { ID = i })
.ToArray();

var loadOptions = new SampleLoadOptions {
SuppressGuardNulls = true,

PrimaryKey = new[] { "ID" },
PaginateViaPrimaryKey = true,

Filter = new[] { "ID", ">", "0" },

Skip = 2,
Take = 2
};

var loadResult = DataSourceLoader.Load(data, loadOptions);

Assert.Equal(
"[{ID:3},{ID:4}]",
DataToString(loadResult.data)
);

var log = loadOptions.ExpressionLog;

Assert.EndsWith(
".Where(obj => (obj.ID > 0))" +
".OrderBy(obj => obj.ID)" +
".Select(obj => new AnonType`1(I0 = obj.ID))" +
".Skip(2).Take(2)",
log[0]
);

Assert.EndsWith(
".Where(obj => ((obj.ID == 3) OrElse (obj.ID == 4)))" +
".OrderBy(obj => obj.ID)",
log[1]
);
}

[Fact]
public void MultiKey() {
var data = Enumerable.Range(0, 5)
.Select(i => new { K1 = i, K2 = 5 - i })
.ToArray();

var loadOptions = new SampleLoadOptions {
SuppressGuardNulls = true,

PrimaryKey = new[] { "K1", "K2" },
PaginateViaPrimaryKey = true,

Skip = 3,
Take = 2
};

DataSourceLoader.Load(data, loadOptions);

Assert.Contains(
".Where(obj => (((obj.K1 == 3) AndAlso (obj.K2 == 2)) OrElse ((obj.K1 == 4) AndAlso (obj.K2 == 1))))",
loadOptions.ExpressionLog[1]
);
}

[Fact]
public void NotUsedWoSkip() {
var loadOptions = new SampleLoadOptions {
PaginateViaPrimaryKey = true,
Take = 123
};

DataSourceLoader.Load(new object[0], loadOptions);

Assert.All(
loadOptions.ExpressionLog,
i => Assert.DoesNotContain(".Select(", i)
);
}

[Fact]
public void RequiresPrimaryKey() {
var error = Record.Exception(delegate {
DataSourceLoader.Load(new string[0], new SampleLoadOptions {
PaginateViaPrimaryKey = true,
Skip = 1
});
});
Assert.True(error is InvalidOperationException);
}

static string DataToString(object data) {
return JsonConvert.SerializeObject(data).Replace("\"", "");
}

}

}
16 changes: 9 additions & 7 deletions net/DevExtreme.AspNet.Data/DataSourceExpressionBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using DevExtreme.AspNet.Data.RemoteGrouping;
using DevExtreme.AspNet.Data.Types;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

Expand All @@ -17,8 +19,8 @@ public DataSourceExpressionBuilder(DataSourceLoadContext context, bool guardNull
_anonTypeNewTweaks = anonTypeNewTweaks;
}

public Expression BuildLoadExpr(Expression source, bool paginate = true) {
return BuildCore(source, paginate: paginate);
public Expression BuildLoadExpr(Expression source, bool paginate = true, IList filterOverride = null, IReadOnlyList<string> selectOverride = null) {
return BuildCore(source, paginate: paginate, filterOverride: filterOverride, selectOverride: selectOverride);
}

public Expression BuildCountExpr(Expression source) {
Expand All @@ -29,19 +31,19 @@ public Expression BuildLoadGroupsExpr(Expression source) {
return BuildCore(source, remoteGrouping: true);
}

Expression BuildCore(Expression expr, bool paginate = false, bool isCountQuery = false, bool remoteGrouping = false) {
Expression BuildCore(Expression expr, bool paginate = false, bool isCountQuery = false, bool remoteGrouping = false, IList filterOverride = null, IReadOnlyList<string> selectOverride = null) {
var queryableType = typeof(Queryable);
var genericTypeArguments = new[] { typeof(T) };

if(_context.HasFilter)
expr = Expression.Call(queryableType, "Where", genericTypeArguments, expr, Expression.Quote(new FilterExpressionCompiler<T>(_guardNulls, _context.UseStringToLower).Compile(_context.Filter)));
if(filterOverride != null || _context.HasFilter)
expr = Expression.Call(queryableType, "Where", genericTypeArguments, expr, Expression.Quote(new FilterExpressionCompiler<T>(_guardNulls, _context.UseStringToLower).Compile(filterOverride ?? _context.Filter)));

if(!isCountQuery) {
if(!remoteGrouping) {
if(_context.HasAnySort)
expr = new SortExpressionCompiler<T>(_guardNulls).Compile(expr, _context.GetFullSort());
if(_context.HasAnySelect && _context.UseRemoteSelect) {
expr = new SelectExpressionCompiler<T>(_guardNulls).Compile(expr, _context.FullSelect);
if(selectOverride != null || _context.HasAnySelect && _context.UseRemoteSelect) {
expr = new SelectExpressionCompiler<T>(_guardNulls).Compile(expr, selectOverride ?? _context.FullSelect);
genericTypeArguments = expr.Type.GetGenericArguments();
}
} else {
Expand Down
5 changes: 3 additions & 2 deletions net/DevExtreme.AspNet.Data/DataSourceLoadContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ partial class DataSourceLoadContext {
partial class DataSourceLoadContext {
public int Skip => _options.Skip;
public int Take => _options.Take;
public bool PaginateViaPrimaryKey => _options.PaginateViaPrimaryKey.GetValueOrDefault(false);
}

// Filter
Expand Down Expand Up @@ -111,7 +112,7 @@ partial class DataSourceLoadContext {

bool HasSort => !IsEmpty(_options.Sort);

IReadOnlyList<string> PrimaryKey {
public IReadOnlyList<string> PrimaryKey {
get {
EnsurePrimaryKeyAndDefaultSort();
return _primaryKey;
Expand All @@ -125,7 +126,7 @@ string DefaultSort {
}
}

bool HasPrimaryKey => !IsEmpty(PrimaryKey);
public bool HasPrimaryKey => !IsEmpty(PrimaryKey);

bool HasDefaultSort => !String.IsNullOrEmpty(DefaultSort);

Expand Down
Loading

0 comments on commit 5550aad

Please sign in to comment.