Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PaginateViaPrimaryKey option #346

Merged
merged 11 commits into from
Mar 27, 2019
Merged
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