Skip to content
This repository was archived by the owner on Feb 17, 2025. It is now read-only.

Commit 5182f6a

Browse files
author
IharYakimush
authored
Merge pull request #14 from IharYakimush/develop
release 1.3 top skip support
2 parents 803fef8 + 12b91da commit 5182f6a

File tree

7 files changed

+274
-34
lines changed

7 files changed

+274
-34
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
namespace Community.OData.Linq.xTests
2+
{
3+
using System.Collections;
4+
using System.Linq;
5+
6+
using Community.OData.Linq.xTests.SampleData;
7+
8+
using Microsoft.OData;
9+
10+
using Xunit;
11+
12+
public class TopSkipTests
13+
{
14+
[Theory]
15+
[InlineData("1", null, 1, "n1")]
16+
[InlineData("1", "0", 1, "n1")]
17+
[InlineData(null, "1", 1, "n2")]
18+
[InlineData(null, null, 2, "n1")]
19+
[InlineData("0", "1", 0, null)]
20+
[InlineData("0", "0", 0, null)]
21+
public void TopSkip(string top, string skip, int count, string expectedName)
22+
{
23+
var result = SimpleClass.CreateQuery().OData().TopSkip(top, skip).ToArray();
24+
25+
Assert.Equal(count, result.Length);
26+
if (count > 0)
27+
{
28+
Assert.Equal(expectedName, result[0].Name);
29+
}
30+
}
31+
32+
[Theory]
33+
[InlineData(null, 20)]
34+
[InlineData(10, 10)]
35+
[InlineData(30, 30)]
36+
public void TopSkipDefaultPageSize(int? pageSize, int count)
37+
{
38+
var result = Enumerable.Repeat(new SimpleClass(), 1000).AsQueryable().OData(s => s.QuerySettings.PageSize = pageSize ?? s.QuerySettings.PageSize)
39+
.TopSkip().ToArray();
40+
41+
Assert.Equal(count, result.Length);
42+
}
43+
44+
[Theory]
45+
[InlineData("-1", "1", "Invalid value '-1' for $top query option found.")]
46+
[InlineData("1", "-1", "Invalid value '-1' for $skip query option found.")]
47+
[InlineData("1000000000000000", "1", "The limit of '2147483647' for Top query has been exceeded.")]
48+
[InlineData("1", "1000000000000000", "The limit of '2147483647' for Skip query has been exceeded.")]
49+
public void TopSkipValidation(string top, string skip, string expectedMessage)
50+
{
51+
var message = Assert.Throws<ODataException>(() => SimpleClass.CreateQuery().OData().TopSkip(top, skip))
52+
.Message;
53+
Assert.Contains(expectedMessage, message);
54+
}
55+
56+
[Theory]
57+
[InlineData("11", "1", "The limit of '10' for Top query has been exceeded.")]
58+
[InlineData("1", "11", "The limit of '10' for Skip query has been exceeded.")]
59+
public void TopSkipMax(string top, string skip, string expectedMessage)
60+
{
61+
var message = Assert.Throws<ODataException>(() => SimpleClass.CreateQuery().OData(s =>
62+
{
63+
s.ValidationSettings.MaxTop = 10;
64+
s.ValidationSettings.MaxSkip = 10;
65+
}).TopSkip(top, skip))
66+
.Message;
67+
Assert.Contains(expectedMessage, message);
68+
}
69+
}
70+
}

Community.Data.OData.Linq/Community.OData.Linq.csproj

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,15 @@
1010
<PackageLicenseUrl>https://github.com/IharYakimush/comminity-data-odata-linq/blob/master/LICENSE</PackageLicenseUrl>
1111
<PackageProjectUrl>https://github.com/IharYakimush/comminity-data-odata-linq</PackageProjectUrl>
1212
<RepositoryType></RepositoryType>
13-
<PackageTags>odata filter linq</PackageTags>
14-
<PackageReleaseNotes>Fixed SelectExpand usage with EntityFramework
15-
Fixed usage of OData linq extensions after OrderBy
16-
Allowed filter by dynamic properties for open types in InMemory providers (https://github.com/IharYakimush/comminity-data-odata-linq/issues/10)</PackageReleaseNotes>
17-
<Description>Use OData filter text query in linq expresson for any IQuerable without ASP.NET dependency</Description>
13+
<PackageTags>odata filter linq netstandard</PackageTags>
14+
<PackageReleaseNotes>Added support for $top and $skip parameters</PackageReleaseNotes>
15+
<Description>Use OData filter text query in linq expresson for any IQuerable without ASP.NET dependency. Support netstandard2.0</Description>
1816
<Company />
1917
<RepositoryUrl></RepositoryUrl>
20-
<Version>1.2.0</Version>
18+
<Version>1.3.0</Version>
2119
<NeutralLanguage>en</NeutralLanguage>
22-
<AssemblyVersion>1.2.0.0</AssemblyVersion>
23-
<FileVersion>1.2.0.0</FileVersion>
20+
<AssemblyVersion>1.3.0.0</AssemblyVersion>
21+
<FileVersion>1.3.0.0</FileVersion>
2422
</PropertyGroup>
2523

2624
<ItemGroup>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
namespace Community.OData.Linq.OData.Query
2+
{
3+
using System.Linq;
4+
5+
using Community.OData.Linq.Common;
6+
using Community.OData.Linq.Properties;
7+
8+
using Microsoft.OData;
9+
10+
public class TopSkipHelper
11+
{
12+
public static IQueryable<T> ApplyTopWithValidation<T>(IQueryable<T> query, long? top, ODataSettings settings)
13+
{
14+
if (top.HasValue)
15+
{
16+
if (top.Value > int.MaxValue)
17+
{
18+
throw new ODataException(
19+
Error.Format(
20+
SRResources.SkipTopLimitExceeded,
21+
int.MaxValue,
22+
AllowedQueryOptions.Top,
23+
top.Value));
24+
}
25+
26+
if (top.Value > settings.ValidationSettings.MaxTop)
27+
{
28+
throw new ODataException(
29+
Error.Format(
30+
SRResources.SkipTopLimitExceeded,
31+
settings.ValidationSettings.MaxTop,
32+
AllowedQueryOptions.Top,
33+
top.Value));
34+
}
35+
36+
IQueryable<T> result = ExpressionHelpers.Take(
37+
query,
38+
(int)top.Value,
39+
typeof(T),
40+
settings.QuerySettings.EnableConstantParameterization) as IQueryable<T>;
41+
42+
return result;
43+
}
44+
45+
return query;
46+
}
47+
48+
public static IQueryable<T> ApplySkipWithValidation<T>(IQueryable<T> query, long? skip, ODataSettings settings)
49+
{
50+
if (skip.HasValue)
51+
{
52+
if (skip.Value > int.MaxValue)
53+
{
54+
throw new ODataException(
55+
Error.Format(
56+
SRResources.SkipTopLimitExceeded,
57+
int.MaxValue,
58+
AllowedQueryOptions.Skip,
59+
skip.Value));
60+
}
61+
62+
if (skip.Value > settings.ValidationSettings.MaxSkip)
63+
{
64+
throw new ODataException(
65+
Error.Format(
66+
SRResources.SkipTopLimitExceeded,
67+
settings.ValidationSettings.MaxSkip,
68+
AllowedQueryOptions.Skip,
69+
skip.Value));
70+
}
71+
72+
IQueryable<T> result = ExpressionHelpers.Skip(
73+
query,
74+
(int)skip.Value,
75+
typeof(T),
76+
settings.QuerySettings.EnableConstantParameterization) as IQueryable<T>;
77+
78+
return result;
79+
}
80+
81+
return query;
82+
}
83+
}
84+
}

Community.Data.OData.Linq/ODataSettings.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,45 @@
11
namespace Community.OData.Linq
22
{
3+
using System;
4+
35
using Community.OData.Linq.OData.Query;
46

57
using Microsoft.OData.UriParser;
68

79
public class ODataSettings
810
{
11+
private static readonly object SyncObj = new object();
12+
13+
private static Action<ODataSettings> Initializer = null;
14+
15+
/// <summary>
16+
/// Sets the action which will be used to initialize every instance of <type ref="ODataSettings"></type>.
17+
/// </summary>
18+
/// <param name="initializer">The action which will be used to initialize every instance of <type ref="ODataSettings"></type>.</param>
19+
/// <exception cref="System.ArgumentNullException">initializer</exception>
20+
/// <exception cref="System.InvalidOperationException">SetInitializer</exception>
21+
public static void SetInitializer(Action<ODataSettings> initializer)
22+
{
23+
if (initializer == null) throw new ArgumentNullException(nameof(initializer));
24+
25+
if (Initializer == null)
26+
{
27+
lock (SyncObj)
28+
{
29+
if (Initializer == null)
30+
{
31+
Initializer = initializer;
32+
return;
33+
}
34+
}
35+
}
36+
37+
throw new InvalidOperationException($"{nameof(SetInitializer)} method can be invoked only once");
38+
}
39+
940
internal static ODataUriResolver DefaultResolver = new StringAsEnumResolver { EnableCaseInsensitive = true };
1041

11-
public ODataQuerySettings QuerySettings { get; } = new ODataQuerySettings() { PageSize = 20 };
42+
public ODataQuerySettings QuerySettings { get; } = new ODataQuerySettings { PageSize = 20 };
1243

1344
public ODataValidationSettings ValidationSettings { get; } = new ODataValidationSettings();
1445

@@ -27,5 +58,10 @@ public class ODataSettings
2758
EnableSelect = true,
2859
MaxTop = 100
2960
};
61+
62+
public ODataSettings()
63+
{
64+
Initializer?.Invoke(this);
65+
}
3066
}
3167
}

Community.Data.OData.Linq/OdataLinqExtensions.cs

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99

1010
using Community.OData.Linq.Builder;
1111
using Community.OData.Linq.Builder.Validators;
12+
using Community.OData.Linq.Common;
1213
using Community.OData.Linq.OData;
1314
using Community.OData.Linq.OData.Query;
14-
using Community.OData.Linq.OData.Query.Expressions;
15+
using Community.OData.Linq.OData.Query.Expressions;
16+
using Community.OData.Linq.Properties;
1517

1618
using Microsoft.Extensions.DependencyInjection;
1719
using Microsoft.OData;
@@ -127,28 +129,72 @@ public static IEnumerable<ISelectExpandWrapper> SelectExpand<T>(
127129
return Enumerate<ISelectExpandWrapper>(result);
128130
}
129131

132+
public static ODataQuery<T> TopSkip<T>(this ODataQuery<T> query, string topText = null, string skipText = null, string entitySetName = null)
133+
{
134+
if (query == null) throw new ArgumentNullException(nameof(query));
135+
136+
ODataSettings settings = query.ServiceProvider.GetRequiredService<ODataSettings>();
137+
138+
Dictionary<string, string> dictionary = new Dictionary<string, string>();
139+
140+
if (topText != null)
141+
{
142+
dictionary.Add("$top", topText);
143+
}
144+
145+
if (skipText != null)
146+
{
147+
dictionary.Add("$skip", skipText);
148+
}
149+
150+
ODataQueryOptionParser queryOptionParser = GetParser(
151+
query,
152+
entitySetName,
153+
dictionary);
154+
155+
long? skip = queryOptionParser.ParseSkip();
156+
long? top = queryOptionParser.ParseTop();
157+
158+
if (skip.HasValue || top.HasValue || settings.QuerySettings.PageSize.HasValue)
159+
{
160+
IQueryable<T> result = TopSkipHelper.ApplySkipWithValidation(query, skip, settings);
161+
if (top.HasValue)
162+
{
163+
result = TopSkipHelper.ApplyTopWithValidation(result, top, settings);
164+
}
165+
else
166+
{
167+
result = TopSkipHelper.ApplyTopWithValidation(result, settings.QuerySettings.PageSize, settings);
168+
}
169+
170+
return new ODataQuery<T>(result, query.ServiceProvider);
171+
}
172+
173+
return query;
174+
}
175+
130176
/// <summary>
131-
/// The Filter.
132-
/// </summary>
133-
/// <param name="query">
134-
/// The OData aware query.
135-
/// </param>
136-
/// <param name="filterText">
137-
/// The $filter parameter text.
138-
/// </param>
139-
/// <param name="entitySetName">
140-
/// The entity set name.
141-
/// </param>
142-
/// <typeparam name="T">
143-
/// The query type param
144-
/// </typeparam>
145-
/// <returns>
146-
/// The <see cref="ODataQuery{T}"/> query with applied filter parameter.
147-
/// </returns>
148-
/// <exception cref="ArgumentNullException">
149-
/// Argument Null Exception
150-
/// </exception>
151-
public static ODataQuery<T> Filter<T>(this ODataQuery<T> query, string filterText, string entitySetName = null)
177+
/// The Filter.
178+
/// </summary>
179+
/// <param name="query">
180+
/// The OData aware query.
181+
/// </param>
182+
/// <param name="filterText">
183+
/// The $filter parameter text.
184+
/// </param>
185+
/// <param name="entitySetName">
186+
/// The entity set name.
187+
/// </param>
188+
/// <typeparam name="T">
189+
/// The query type param
190+
/// </typeparam>
191+
/// <returns>
192+
/// The <see cref="ODataQuery{T}"/> query with applied filter parameter.
193+
/// </returns>
194+
/// <exception cref="ArgumentNullException">
195+
/// Argument Null Exception
196+
/// </exception>
197+
public static ODataQuery<T> Filter<T>(this ODataQuery<T> query, string filterText, string entitySetName = null)
152198
{
153199
if (query == null) throw new ArgumentNullException(nameof(query));
154200
if (filterText == null) throw new ArgumentNullException(nameof(filterText));
@@ -161,7 +207,7 @@ public static ODataQuery<T> Filter<T>(this ODataQuery<T> query, string filterTex
161207
new Dictionary<string, string> { { "$filter", filterText } });
162208

163209
ODataSettings settings = query.ServiceProvider.GetRequiredService<ODataSettings>();
164-
210+
165211
FilterClause filterClause = queryOptionParser.ParseFilter();
166212
SingleValueNode filterExpression = filterClause.Expression.Accept(
167213
new ParameterAliasNodeTranslator(queryOptionParser.ParameterAliasNodes)) as SingleValueNode;
@@ -225,7 +271,7 @@ public static ODataQueryOrdered<T> OrderBy<T>(this ODataQuery<T> query, string o
225271
IOrderedQueryable<T> result = (IOrderedQueryable<T>)OrderByBinder.OrderApplyToCore(query, settings.QuerySettings, nodes, edmModel);
226272

227273
return new ODataQueryOrdered<T>(result, query.ServiceProvider);
228-
}
274+
}
229275

230276
private static IEnumerable<T> Enumerate<T>(IQueryable queryable) where T : class
231277
{

Community.OData.Linq.Demo/Program.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
{
33
using System;
44

5+
using Community.OData.Linq;
6+
57
class Program
68
{
79
static void Main(string[] args)
810
{
11+
ODataSettings.SetInitializer(s => s.ValidationSettings.MaxTop = 1000);
12+
913
GetStartedDemo.Demo();
1014
Console.WriteLine();
1115

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ https://dotnetfiddle.net/7Ndwot
3131
};
3232
IQueryable<Entity> query = items.AsQueryable();
3333
34-
var result = query.OData().Filter("Id eq 1 or Name eq 'n3'").OrderBy("Name desc").ToArray();
34+
var result = query.OData().Filter("Id eq 1 or Name eq 'n3'").OrderBy("Name desc").TopSkip("10", "0").ToArray();
3535
3636
// Id: 3 Name: n3
3737
// Id: 1 Name: n1
@@ -50,6 +50,8 @@ https://github.com/IharYakimush/comminity-data-odata-linq/wiki
5050
- $orderby
5151
- $select
5252
- $expand
53+
- $top
54+
- $skip
5355

5456
# Nuget
5557
- https://www.nuget.org/packages/Community.OData.Linq

0 commit comments

Comments
 (0)