Skip to content

Commit 7288a4d

Browse files
committed
Merge branch 'main' into dev/29-asynchronous-support
2 parents 9ef40fc + 09d5000 commit 7288a4d

File tree

11 files changed

+319
-173
lines changed

11 files changed

+319
-173
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: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
using Extensions.Test;
14+
using SubSonic.Extensions.Test.Models;
15+
16+
[TestFixture]
17+
public class ExtensionMethodTests
18+
: BaseTestFixture
19+
{
20+
protected override void SetSelectBehaviors()
21+
{
22+
base.SetSelectBehaviors();
23+
24+
string
25+
person_min = @"SELECT MIN([T1].[ID])
26+
FROM [dbo].[Person] AS [T1]",
27+
person_max = @"SELECT MAX([T1].[ID])
28+
FROM [dbo].[Person] AS [T1]",
29+
renter_sum = @"SELECT SUM([T1].[Rent])
30+
FROM [dbo].[Renter] AS [T1]
31+
WHERE ([T1].[PersonID] = @personid_1)",
32+
renter_avg = @"SELECT AVG([T1].[Rent])
33+
FROM [dbo].[Renter] AS [T1]
34+
WHERE ([T1].[PersonID] = @personid_1)";
35+
36+
Context.Database.Instance.AddCommandBehavior(person_max, cmd => People.Max(x => x.ID));
37+
Context.Database.Instance.AddCommandBehavior(person_min, cmd => People.Min(x => x.ID));
38+
Context.Database.Instance.AddCommandBehavior(renter_sum, cmd => Renters.Where(x => x.PersonID == cmd.Parameters["@personid_1"].GetValue<int>()).Sum(x => x.Rent));
39+
Context.Database.Instance.AddCommandBehavior(renter_avg, cmd => Renters.Where(x => x.PersonID == cmd.Parameters["@personid_1"].GetValue<int>()).Average(x => x.Rent));
40+
}
41+
42+
43+
[Test]
44+
public void TheCountMethodIsSupported()
45+
{
46+
Context.People.Count().Should().BeGreaterThan(0).And.IsOfType<int>();
47+
}
48+
49+
[Test]
50+
public void TheLongCountMethodIsSupported()
51+
{
52+
Context.People.LongCount().Should().BeGreaterThan(0).And.IsOfType<long>();
53+
}
54+
55+
[Test]
56+
public void TheMinMethodIsSupported()
57+
{
58+
Context.People.Min(x => x.ID).Should().Be(1);
59+
}
60+
61+
[Test]
62+
public void TheMaxMethodIsSupported()
63+
{
64+
Context.People.Max(x => x.ID).Should().Be(People.Count);
65+
}
66+
67+
[Test]
68+
public void TheSumMethodIsSupported()
69+
{
70+
Person person = Context.People.Single(x => x.ID == 1);
71+
72+
person.Renters.Sum(x => x.Rent).Should().Be(Renters.Where(x => x.PersonID == person.ID).Sum(x => x.Rent));
73+
}
74+
75+
[Test]
76+
public void TheAverageMethodIsSupported()
77+
{
78+
Person person = Context.People.Single(x => x.ID == 1);
79+
80+
person.Renters.Average(x => x.Rent).Should().Be(Renters.Where(x => x.PersonID == person.ID).Average(x => x.Rent));
81+
}
82+
}
83+
}

SubSonic.Tests/DAL/SUT/BaseTestFixture.cs

Lines changed: 41 additions & 6 deletions
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 },
@@ -73,11 +76,11 @@ public virtual void SetupTestFixture()
7376

7477
Renters = new List<Renter>()
7578
{
76-
new Renter() { PersonID = 1, UnitID = 1, StartDate = new DateTime(1980, 01, 01), EndDate = new DateTime(1990, 02, 28) },
77-
new Renter() { PersonID = 2, UnitID = 1, StartDate = new DateTime(1990, 03, 01) },
78-
new Renter() { PersonID = 3, UnitID = 2, StartDate = new DateTime(1980, 03, 01), EndDate = new DateTime(2000, 01, 01) },
79-
new Renter() { PersonID = 1, UnitID = 3, StartDate = new DateTime(1990, 03, 01) },
80-
new Renter() { PersonID = 4, UnitID = 4, StartDate = new DateTime(2000, 01, 01) }
79+
new Renter() { PersonID = 1, UnitID = 1, Rent = 100M, StartDate = new DateTime(1980, 01, 01), EndDate = new DateTime(1990, 02, 28) },
80+
new Renter() { PersonID = 2, UnitID = 1, Rent = 150M, StartDate = new DateTime(1990, 03, 01) },
81+
new Renter() { PersonID = 3, UnitID = 2, Rent = 200M, StartDate = new DateTime(1980, 03, 01), EndDate = new DateTime(2000, 01, 01) },
82+
new Renter() { PersonID = 1, UnitID = 3, Rent = 250M, StartDate = new DateTime(1990, 03, 01) },
83+
new Renter() { PersonID = 4, UnitID = 4, Rent = 300M, StartDate = new DateTime(2000, 01, 01) }
8184
};
8285

8386
RealEstateProperties = new List<RealEstateProperty>()
@@ -97,7 +100,39 @@ 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+
renter_byperson = @"SELECT [T1].[PersonID], [T1].[UnitID], [T1].[Rent], [T1].[StartDate], [T1].[EndDate]
122+
FROM [dbo].[Renter] AS [T1]
123+
WHERE ([T1].[PersonID] == @personid_1)";
124+
125+
Context.Database.Instance.AddCommandBehavior(people_all, cmd => People.ToDataTable());
126+
Context.Database.Instance.AddCommandBehavior(people_all_count, cmd => People.Count());
127+
Context.Database.Instance.AddCommandBehavior(people_all_long_count, cmd => People.LongCount());
128+
Context.Database.Instance.AddCommandBehavior(people_greater_than, cmd => People.Where(x => x.ID > cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
129+
Context.Database.Instance.AddCommandBehavior(people_equal, cmd => People.Where(x => x.ID == cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
130+
Context.Database.Instance.AddCommandBehavior(people_less_than, cmd => People.Where(x => x.ID < cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
131+
132+
Context.Database.Instance.AddCommandBehavior(renter_byperson, cmd => Renters.Where(x => x.PersonID == cmd.Parameters["@personid_1"].GetValue<int>()).ToDataTable());
133+
}
134+
135+
protected virtual void SetInsertBehaviors()
101136
{
102137
string
103138
insert_person = @"INSERT INTO [dbo].[Person]

SubSonic/Infrastructure/Builders/DbSqlQueryBuilder/DbSqlQueryBuilderQueryProvider.cs

Lines changed: 103 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,116 @@ 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+
switch (lambda.Body.NodeType)
57+
{
58+
case ExpressionType.MemberAccess:
59+
dbSelect = (DbSelectExpression)BuildSelect(dbSelect, dbSelect.Columns.Where(x => x.PropertyName.Equals(lambda.GetProperty().Name, StringComparison.Ordinal)));
60+
break;
61+
default:
62+
where = BuildWhere(dbSelect, lambda);
63+
break;
64+
}
65+
}
66+
}
67+
}
68+
69+
if (call.Method.Name.In(nameof(Queryable.Single), nameof(Queryable.SingleOrDefault), nameof(Queryable.First), nameof(Queryable.FirstOrDefault)))
70+
{
71+
object result = Execute<TResult>(BuildSelect(dbSelect, where));
72+
73+
if (result is TResult matched)
74+
{
75+
return matched;
76+
}
77+
else if (result is IEnumerable<TResult> enumerable)
78+
{
79+
return enumerable.Any() ? enumerable.ElementAt(0) : default(TResult);
80+
}
81+
#if NETSTANDARD2_0
82+
else if (call.Method.Name.Contains("Default"))
83+
#elif NETSTANDARD2_1
84+
else if (call.Method.Name.Contains("Default", StringComparison.CurrentCulture))
85+
#endif
86+
{
87+
return default(TResult);
88+
}
89+
else
90+
{
91+
throw Error.InvalidOperation($"Method {call.Method.Name} expects data.");
92+
}
93+
}
94+
else if (call.Method.Name.In(nameof(Queryable.Count), nameof(Queryable.LongCount), nameof(Queryable.Min), nameof(Queryable.Max), nameof(Queryable.Sum), nameof(Queryable.Average)))
95+
{
96+
if (BuildSelect(dbSelect, where) is DbSelectExpression select)
97+
{
98+
if (!Enum.TryParse<AggregateType>(call.Method.Name, out AggregateType aggregateType))
99+
{
100+
throw Error.NotSupported(SubSonicErrorMessages.MethodNotSupported.Format(call.Method.Name));
101+
}
102+
103+
Expression argument = null;
104+
105+
if (select.Columns.Count > 1)
106+
{
107+
argument = select.Columns.First(x => x.Property.IsPrimaryKey).Expression;
108+
}
109+
else
110+
{
111+
argument = select.Columns.Single().Expression;
112+
}
113+
114+
TResult result = Execute<TResult>(DbExpression.DbSelectAggregate(select, new[]
115+
{
116+
DbExpression.DbAggregate(typeof(TResult), aggregateType, argument)
117+
}));
118+
119+
if (select.Take is ConstantExpression take)
120+
{
121+
if (result.IsIntGreaterThan(take.Value))
122+
{
123+
return (TResult)Convert.ChangeType(take.Value, typeof(TResult), CultureInfo.InvariantCulture);
124+
}
125+
}
126+
127+
return result;
128+
}
129+
}
130+
131+
throw Error.NotSupported(SubSonicErrorMessages.ExpressionNotSupported.Format(call.Method.Name));
132+
}
133+
36134
public TResult Execute<TResult>(Expression expression)
37135
{
38136
if (expression is null)
39137
{
40138
throw new ArgumentNullException(nameof(expression));
41139
}
42140

43-
if (expression is DbExpression query)
141+
if (expression is MethodCallExpression call)
142+
{ // execution request originates from the System.Linq namespace
143+
return ExecuteMethod<TResult>(call);
144+
}
145+
else if (expression is DbExpression query)
44146
{ // execution request is from the subsonic namespace
45147
using (SharedDbConnectionScope Scope = DbContext.ServiceProvider.GetService<SharedDbConnectionScope>())
46148
{
@@ -97,75 +199,6 @@ public TResult Execute<TResult>(Expression expression)
97199
}
98200
}
99201
}
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-
}
169202

170203
throw new NotSupportedException(expression.ToString());
171204
}

0 commit comments

Comments
 (0)