Skip to content

Commit 577a596

Browse files
authored
Merge pull request #40 from SubSonic-Core/dev/37-LongCount
#37 implemented the LongCount extension method
2 parents 69c54cb + 9b28976 commit 577a596

File tree

11 files changed

+236
-168
lines changed

11 files changed

+236
-168
lines changed

SubSonic.Tests/DAL/Asynchronous/AsynchronousTests.cs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,6 @@ namespace SubSonic.Tests.DAL
1919
public class AsynchronousTests
2020
: BaseTestFixture
2121
{
22-
public override void SetupTestFixture()
23-
{
24-
base.SetupTestFixture();
25-
26-
string
27-
people_all = @"SELECT [T1].[ID], [T1].[FirstName], [T1].[MiddleInitial], [T1].[FamilyName], [T1].[FullName]
28-
FROM [dbo].[Person] AS [T1]",
29-
people_all_count = @"SELECT COUNT([T1].[ID])
30-
FROM [dbo].[Person] AS [T1]",
31-
people_equal = @"SELECT [T1].[ID], [T1].[FirstName], [T1].[MiddleInitial], [T1].[FamilyName], [T1].[FullName]
32-
FROM [dbo].[Person] AS [T1]
33-
WHERE ([T1].[ID] = @id_1)",
34-
people_greater_than = @"SELECT [T1].[ID], [T1].[FirstName], [T1].[MiddleInitial], [T1].[FamilyName], [T1].[FullName]
35-
FROM [dbo].[Person] AS [T1]
36-
WHERE ([T1].[ID] > @id_1)",
37-
people_less_than = @"SELECT [T1].[ID], [T1].[FirstName], [T1].[MiddleInitial], [T1].[FamilyName], [T1].[FullName]
38-
FROM [dbo].[Person] AS [T1]
39-
WHERE ([T1].[ID] < @id_1)";
40-
41-
Context.Database.Instance.AddCommandBehavior(people_all, cmd => People.ToDataTable());
42-
Context.Database.Instance.AddCommandBehavior(people_all_count, cmd => People.Count());
43-
Context.Database.Instance.AddCommandBehavior(people_greater_than, cmd => People.Where(x => x.ID > cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
44-
Context.Database.Instance.AddCommandBehavior(people_equal, cmd => People.Where(x => x.ID == cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
45-
Context.Database.Instance.AddCommandBehavior(people_less_than, cmd => People.Where(x => x.ID < cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
46-
}
47-
4822
[Test]
4923
public async Task ShouldBeAbleToGetSingleAsync()
5024
{
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using FluentAssertions;
2+
using NUnit.Framework;
3+
using SubSonic.Tests.DAL.SUT;
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
11+
namespace SubSonic.Tests.DAL.ExtensionMethod
12+
{
13+
[TestFixture]
14+
public class ExtensionMethodTests
15+
: BaseTestFixture
16+
{
17+
[Test]
18+
public void TheCountMethodIsSupported()
19+
{
20+
Context.People.Count().Should().BeGreaterThan(0).And.IsOfType<int>();
21+
}
22+
23+
[Test]
24+
public void TheLongCountMethodIsSupported()
25+
{
26+
Context.People.LongCount().Should().BeGreaterThan(0).And.IsOfType<long>();
27+
}
28+
}
29+
}

SubSonic.Tests/DAL/SUT/BaseTestFixture.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections;
88
using System.Collections.Generic;
99
using System.Data.Common;
10+
using System.Linq;
1011

1112
namespace SubSonic.Tests.DAL.SUT
1213
{
@@ -56,6 +57,8 @@ public virtual void SetupTestFixture()
5657

5758
SetInsertBehaviors();
5859

60+
SetSelectBehaviors();
61+
5962
Statuses = new List<Status>()
6063
{
6164
new Status() { ID = 1, Name = "Vacant", IsAvailableStatus = true },
@@ -97,7 +100,34 @@ public virtual void SetupTestFixture()
97100
};
98101
}
99102

100-
private void SetInsertBehaviors()
103+
protected virtual void SetSelectBehaviors()
104+
{
105+
string
106+
people_all = @"SELECT [T1].[ID], [T1].[FirstName], [T1].[MiddleInitial], [T1].[FamilyName], [T1].[FullName]
107+
FROM [dbo].[Person] AS [T1]",
108+
people_all_count = @"SELECT COUNT([T1].[ID])
109+
FROM [dbo].[Person] AS [T1]",
110+
people_all_long_count = @"SELECT COUNT_BIG([T1].[ID])
111+
FROM [dbo].[Person] AS [T1]",
112+
people_equal = @"SELECT [T1].[ID], [T1].[FirstName], [T1].[MiddleInitial], [T1].[FamilyName], [T1].[FullName]
113+
FROM [dbo].[Person] AS [T1]
114+
WHERE ([T1].[ID] = @id_1)",
115+
people_greater_than = @"SELECT [T1].[ID], [T1].[FirstName], [T1].[MiddleInitial], [T1].[FamilyName], [T1].[FullName]
116+
FROM [dbo].[Person] AS [T1]
117+
WHERE ([T1].[ID] > @id_1)",
118+
people_less_than = @"SELECT [T1].[ID], [T1].[FirstName], [T1].[MiddleInitial], [T1].[FamilyName], [T1].[FullName]
119+
FROM [dbo].[Person] AS [T1]
120+
WHERE ([T1].[ID] < @id_1)";
121+
122+
Context.Database.Instance.AddCommandBehavior(people_all, cmd => People.ToDataTable());
123+
Context.Database.Instance.AddCommandBehavior(people_all_count, cmd => People.Count());
124+
Context.Database.Instance.AddCommandBehavior(people_all_long_count, cmd => People.LongCount());
125+
Context.Database.Instance.AddCommandBehavior(people_greater_than, cmd => People.Where(x => x.ID > cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
126+
Context.Database.Instance.AddCommandBehavior(people_equal, cmd => People.Where(x => x.ID == cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
127+
Context.Database.Instance.AddCommandBehavior(people_less_than, cmd => People.Where(x => x.ID < cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
128+
}
129+
130+
protected virtual void SetInsertBehaviors()
101131
{
102132
string
103133
insert_person = @"INSERT INTO [dbo].[Person]

SubSonic/Infrastructure/Builders/DbSqlQueryBuilder/DbSqlQueryBuilderQueryProvider.cs

Lines changed: 84 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,97 @@ public IQueryable<TEntity> CreateQuery<TEntity>(Expression expression)
3333
return new SubSonicCollection<TEntity>(this, BuildQuery(expression));
3434
}
3535

36+
public TResult ExecuteMethod<TResult>(MethodCallExpression call)
37+
{
38+
if (call is null)
39+
{
40+
throw Error.ArgumentNull(nameof(call));
41+
}
42+
43+
DbSelectExpression dbSelect = null;
44+
Expression where = null;
45+
46+
for (int i = 0, n = call.Arguments.Count; i < n; i++)
47+
{
48+
if (call.Arguments[i] is DbSelectExpression select)
49+
{
50+
dbSelect = select;
51+
}
52+
else if (call.Arguments[i] is UnaryExpression unary)
53+
{
54+
if (unary.Operand is LambdaExpression lambda)
55+
{
56+
where = BuildWhere(dbSelect, lambda);
57+
}
58+
}
59+
}
60+
61+
if (call.Method.Name.In(nameof(Queryable.Single), nameof(Queryable.SingleOrDefault), nameof(Queryable.First), nameof(Queryable.FirstOrDefault)))
62+
{
63+
object result = Execute<TResult>(BuildSelect(dbSelect, where));
64+
65+
if (result is TResult matched)
66+
{
67+
return matched;
68+
}
69+
else if (result is IEnumerable<TResult> enumerable)
70+
{
71+
return enumerable.Any() ? enumerable.ElementAt(0) : default(TResult);
72+
}
73+
#if NETSTANDARD2_0
74+
else if (call.Method.Name.Contains("Default"))
75+
#elif NETSTANDARD2_1
76+
else if (call.Method.Name.Contains("Default", StringComparison.CurrentCulture))
77+
#endif
78+
{
79+
return default(TResult);
80+
}
81+
else
82+
{
83+
throw Error.InvalidOperation($"Method {call.Method.Name} expects data.");
84+
}
85+
}
86+
else if (call.Method.Name.In(nameof(Queryable.Count), nameof(Queryable.LongCount)))
87+
{
88+
if (BuildSelect(dbSelect, where) is DbSelectExpression select)
89+
{
90+
if (!Enum.TryParse<AggregateType>(call.Method.Name, out AggregateType aggregateType))
91+
{
92+
throw Error.NotSupported(SubSonicErrorMessages.MethodNotSupported.Format(call.Method.Name));
93+
}
94+
95+
TResult result = Execute<TResult>(DbExpression.DbSelectAggregate(select, new[]
96+
{
97+
DbExpression.DbAggregate(typeof(TResult), aggregateType, select.Columns.First(x => x.Property.IsPrimaryKey).Expression)
98+
}));
99+
100+
if (select.Take is ConstantExpression take)
101+
{
102+
if (result.IsIntGreaterThan(take.Value))
103+
{
104+
return (TResult)Convert.ChangeType(take.Value, typeof(TResult), CultureInfo.InvariantCulture);
105+
}
106+
}
107+
108+
return result;
109+
}
110+
}
111+
112+
throw Error.NotSupported(SubSonicErrorMessages.ExpressionNotSupported.Format(call.Method.Name));
113+
}
114+
36115
public TResult Execute<TResult>(Expression expression)
37116
{
38117
if (expression is null)
39118
{
40119
throw new ArgumentNullException(nameof(expression));
41120
}
42121

43-
if (expression is DbExpression query)
122+
if (expression is MethodCallExpression call)
123+
{ // execution request originates from the System.Linq namespace
124+
return ExecuteMethod<TResult>(call);
125+
}
126+
else if (expression is DbExpression query)
44127
{ // execution request is from the subsonic namespace
45128
using (SharedDbConnectionScope Scope = DbContext.ServiceProvider.GetService<SharedDbConnectionScope>())
46129
{
@@ -97,75 +180,6 @@ public TResult Execute<TResult>(Expression expression)
97180
}
98181
}
99182
}
100-
else if (expression is MethodCallExpression call)
101-
{ // execution request originates from the System.Linq namespace
102-
103-
DbSelectExpression dbSelect = null;
104-
Expression where = null;
105-
106-
for (int i = 0, n = call.Arguments.Count; i < n; i++)
107-
{
108-
if (call.Arguments[i] is DbSelectExpression select)
109-
{
110-
dbSelect = select;
111-
}
112-
else if (call.Arguments[i] is UnaryExpression unary)
113-
{
114-
if (unary.Operand is LambdaExpression lambda)
115-
{
116-
where = BuildWhere(dbSelect, lambda);
117-
}
118-
}
119-
}
120-
121-
if (call.Method.Name.In(nameof(Queryable.Single), nameof(Queryable.SingleOrDefault), nameof(Queryable.First), nameof(Queryable.FirstOrDefault)))
122-
{
123-
object result = Execute<TResult>(BuildSelect(dbSelect, where));
124-
125-
if (result is TResult matched)
126-
{
127-
return matched;
128-
}
129-
else if (result is IEnumerable<TResult> enumerable)
130-
{
131-
return enumerable.Any() ? enumerable.ElementAt(0) : default(TResult);
132-
}
133-
#if NETSTANDARD2_0
134-
else if (call.Method.Name.Contains("Default"))
135-
#elif NETSTANDARD2_1
136-
else if (call.Method.Name.Contains("Default", StringComparison.CurrentCulture))
137-
#endif
138-
{
139-
return default(TResult);
140-
}
141-
else
142-
{
143-
throw Error.InvalidOperation($"Method {call.Method.Name} expects data.");
144-
}
145-
}
146-
else if (call.Method.Name.In(nameof(Queryable.Count)))
147-
{
148-
if (BuildSelect(dbSelect, where) is DbSelectExpression select)
149-
{
150-
TResult result = Execute<TResult>(DbExpression.DbSelectAggregate(select, new[]
151-
{
152-
DbExpression.DbAggregate(typeof(TResult), AggregateType.Count, select.Columns.First(x => x.Property.IsPrimaryKey).Expression)
153-
}));
154-
155-
if (select.Take is ConstantExpression take)
156-
{
157-
if (result.IsIntGreaterThan(take.Value))
158-
{
159-
return (TResult)Convert.ChangeType(take.Value, typeof(TResult), CultureInfo.InvariantCulture);
160-
}
161-
}
162-
163-
return result;
164-
}
165-
}
166-
167-
throw Error.NotSupported(SubSonicErrorMessages.ExpressionNotSupported.Format(call.Method.Name));
168-
}
169183

170184
throw new NotSupportedException(expression.ToString());
171185
}

0 commit comments

Comments
 (0)