Skip to content

Commit 329eed6

Browse files
committed
#27 down to 1 unit test failing exception in the WhereEnumerableIterator from the runtime library.
1 parent 75d485b commit 329eed6

File tree

15 files changed

+164
-88
lines changed

15 files changed

+164
-88
lines changed

SubSonic.Tests/DAL/DbContext/DbInsertTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ OUTPUT INSERTED.* INTO @output
2424
VALUES
2525
(@FirstName, @MiddleInitial, @FamilyName)";
2626

27-
Models.Person person = new Models.Person(){ FirstName = "First_1", FamilyName = "Last_1", MiddleInitial = "M" };
27+
Models.Person person = GetFakePerson.Generate();
2828

2929
Context.Database.Instance.AddCommandBehavior(expected_cmd, cmd =>
3030
{
@@ -41,7 +41,7 @@ OUTPUT INSERTED.* INTO @output
4141

4242
data.FullName = String.Format("{0}, {1}{2}",
4343
data.FamilyName, data.FirstName,
44-
data.MiddleInitial.IsNotNullOrEmpty() ? $" {data.MiddleInitial}." : "");
44+
string.IsNullOrEmpty(data.MiddleInitial?.Trim()) ? "" : $" {data.MiddleInitial}.");
4545

4646
return new[] { data }.ToDataTable();
4747
});
@@ -53,7 +53,9 @@ OUTPUT INSERTED.* INTO @output
5353
Context.SaveChanges().Should().BeTrue();
5454

5555
person.ID.Should().Be(5);
56-
person.FullName.Should().Be("Last_1, First_1 M.");
56+
person.FullName.Should().Be(String.Format("{0}, {1}{2}",
57+
person.FamilyName, person.FirstName,
58+
string.IsNullOrEmpty(person.MiddleInitial?.Trim()) ? "" : $" {person.MiddleInitial}."));
5759
}
5860

5961
[Test]

SubSonic.Tests/DAL/DbSetCollection/DbSetCollectionTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public override void SetupTestFixture()
2222
base.SetupTestFixture();
2323

2424
string
25+
properties_all = @"SELECT [T1].[ID], [T1].[StatusID], [T1].[HasParallelPowerGeneration]
26+
FROM [dbo].[RealEstateProperty] AS [T1]",
2527
units =
2628
@"SELECT [{0}].[ID], [{0}].[Bedrooms] AS [NumberOfBedrooms], [{0}].[StatusID], [{0}].[RealEstatePropertyID]
2729
FROM [dbo].[Unit] AS [{0}]
@@ -38,6 +40,7 @@ FROM [dbo].[Status] AS [{0}]
3840
FROM [dbo].[Person] AS [{0}]
3941
WHERE ([{0}].[ID] = @id_1)";
4042

43+
Context.Database.Instance.AddCommandBehavior(properties_all, cmd => RealEstateProperties.ToDataTable());
4144
Context.Database.Instance.AddCommandBehavior(units.Format("T1"), cmd => Units.Where(x => x.RealEstatePropertyID == cmd.Parameters["@realestatepropertyid_1"].GetValue<int>()).ToDataTable());
4245
Context.Database.Instance.AddCommandBehavior(status.Format("T1"), cmd => Statuses.Where(x => x.ID == cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
4346
Context.Database.Instance.AddCommandBehavior(statuses.Format("T1"), Statuses);
@@ -58,7 +61,7 @@ public void CanAddNewInstanceToCollection()
5861

5962
Context.RealEstateProperties.Add(property);
6063

61-
IEntityProxy<RealEstateProperty> proxy = (IEntityProxy<RealEstateProperty>)Context.RealEstateProperties.ElementAt(0);
64+
IEntityProxy<RealEstateProperty> proxy = (IEntityProxy<RealEstateProperty>)Context.RealEstateProperties.AsEnumerable().ElementAt(0);
6265

6366
proxy.IsNew.Should().BeTrue();
6467
proxy.Data.HasParallelPowerGeneration.Should().Be(property.HasParallelPowerGeneration);

SubSonic/Data/ChangeTracking/ChangeTrackerElement.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public override void Add(object record)
3939
{
4040
if(record is IEntityProxy<TEntity> entity)
4141
{
42-
if (cache.Any(x => x.IsNew == false && x.KeyData.IsSameAs(entity.KeyData)))
42+
if (!cache.Any(x => x.IsNew == false && x.KeyData.IsSameAs(entity.KeyData)))
4343
{
4444
cache.Add(entity);
4545
}

SubSonic/Error.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,14 @@ public static Exception DivideByZero()
3131
return new DivideByZeroException();
3232
}
3333

34-
public static Exception InvalidOperation()
34+
public static Exception InvalidOperation(string message = null)
3535
{
36-
return new InvalidOperationException();
36+
return new InvalidOperationException(message);
37+
}
38+
39+
public static Exception OutOfBounds()
40+
{
41+
return new IndexOutOfRangeException();
3742
}
3843
}
3944
}

SubSonic/Extensions/Internal/MethodInfo.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
using System;
2-
using System.Collections.Generic;
2+
using System.Linq.Expressions;
33
using System.Reflection;
4-
using System.Text;
5-
using SubSonic.Linq.Expressions;
64

75
namespace SubSonic
86
{
97
using Linq;
10-
8+
using Linq.Expressions;
119
using Legacy = System.Linq.Queryable;
1210

1311
internal static partial class InternalExtensions
@@ -38,5 +36,17 @@ public static bool IsSkip(this MethodInfo info)
3836
{
3937
return info.Name.Equals(nameof(Legacy.Skip), StringComparison.CurrentCulture);
4038
}
39+
40+
public static bool IsDistinct(this MethodInfo info)
41+
{
42+
return info.Name.Equals(nameof(Legacy.Distinct), StringComparison.CurrentCulture);
43+
}
44+
45+
public static bool IsSupportedSelect(this MethodInfo info)
46+
{
47+
return info.GetParameters().Length > 1 &&
48+
info.Name.Equals(nameof(Legacy.Select), StringComparison.CurrentCulture) &&
49+
info.GetParameters()[1].ParameterType.IsSubclassOf(typeof(Expression));
50+
}
4151
}
4252
}

SubSonic/Infrastructure/Builders/DbSqlQueryBuilder/DbSqlQueryBuilder.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,27 @@ public IDbQuery BuildDbQuery<TEntity>(DbQueryType queryType, IEnumerable<IEntity
5858
.Where(x => x.IsNotNull())
5959
.ToArray();
6060

61+
Expression expression = null;
62+
6163
switch (queryType)
6264
{
6365
case DbQueryType.Insert:
64-
return ToQuery(BuildInsertQuery(entities));
66+
expression = BuildInsertQuery(entities);
67+
break;
6568
case DbQueryType.Update:
66-
return ToQuery(BuildUpdateQuery(entities));
69+
expression = BuildUpdateQuery(entities);
70+
break;
6771
case DbQueryType.Delete:
6872
// delete queries can not be alaised.
6973
DbTable = (DbTableExpression)DbExpression.DbTable(DbEntity, null);
7074

71-
return ToQuery(BuildDeleteQuery(entities));
75+
expression = BuildDeleteQuery(entities);
76+
break;
7277
default:
7378
throw new NotSupportedException();
7479
}
80+
81+
return ToQuery(expression);
7582
}
7683

7784
private Expression BuildInsertQuery<TEntity>(IEnumerable<TEntity> entities)

SubSonic/Infrastructure/Builders/DbSqlQueryBuilder/DbSqlQueryBuilderBuildMethods.cs

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public Expression BuildSelect(Expression select, Expression where)
4646
{
4747
if (select is DbSelectExpression _select)
4848
{
49-
return new DbSelectExpression(_select.QueryObject, _select.Type, _select.From, _select.Columns, where, _select.OrderBy, _select.GroupBy, _select.IsDistinct, _select.Take, _select.Skip);
49+
return new DbSelectExpression(_select.QueryObject, _select.Type, _select.From, _select.Columns, where ?? _select.Where, _select.OrderBy, _select.GroupBy, _select.IsDistinct, _select.Take, _select.Skip);
5050
}
5151

5252
throw new NotSupportedException();
@@ -133,12 +133,13 @@ public Expression BuildSelect(Expression select, DbExpressionType eType, IEnumer
133133
return select;
134134
}
135135

136-
public Expression BuildSelect<TEntity, TColumn>(Expression expression, Expression<Func<TEntity, TColumn>> selector)
136+
private Expression BuildSelect(Expression expression, IEnumerable<DbColumnDeclaration> columns)
137137
{
138138
if (expression is DbSelectExpression select)
139139
{
140-
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns.Where(col => col.PropertyName == selector.GetPropertyName()), select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take, select.Skip);
140+
return new DbSelectExpression(select.QueryObject, select.Type, select.From, columns, select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take, select.Skip);
141141
}
142+
142143
return expression;
143144
}
144145
#endregion
@@ -504,6 +505,14 @@ protected virtual Expression BuildQuery(Expression expression)
504505
{
505506
return BuildSelectWithSkip(call);
506507
}
508+
else if (call.Method.IsDistinct())
509+
{
510+
return BuildSelectWithDistinct(call);
511+
}
512+
else if (call.Method.IsSupportedSelect())
513+
{
514+
return BuildSelectWithExpression(call);
515+
}
507516
else
508517
{
509518
throw Error.NotSupported(SubSonicErrorMessages.UnSupportedMethodException.Format(call.Method.Name));
@@ -535,6 +544,54 @@ private static Type[] GetTypeArguments(LambdaExpression expression)
535544
return Array.Empty<Type>();
536545
}
537546

547+
private Expression BuildSelectWithExpression(MethodCallExpression expression)
548+
{
549+
if (!(expression is null))
550+
{
551+
DbSelectExpression select = null;
552+
LambdaExpression selector = null;
553+
554+
foreach (var argument in expression.Arguments)
555+
{
556+
if (argument is DbSelectExpression _select)
557+
{
558+
select = _select;
559+
}
560+
else if (argument is UnaryExpression unary)
561+
{
562+
if (unary.Operand is LambdaExpression lambda)
563+
{
564+
selector = lambda;
565+
}
566+
}
567+
}
568+
569+
return BuildSelect(select, select.Columns.Where(column => column.PropertyName.Equals(selector.GetProperty().Name, StringComparison.CurrentCulture)));
570+
}
571+
572+
return expression;
573+
}
574+
575+
private Expression BuildSelectWithDistinct(MethodCallExpression expression)
576+
{
577+
if (!(expression is null))
578+
{
579+
DbSelectExpression select = null;
580+
581+
foreach (var argument in expression.Arguments)
582+
{
583+
if (argument is DbSelectExpression _select)
584+
{
585+
select = _select;
586+
}
587+
}
588+
589+
return BuildSelect(select, true);
590+
}
591+
592+
return expression;
593+
}
594+
538595
private Expression BuildSelectWithSkip(MethodCallExpression expression)
539596
{
540597
if (!(expression is null))

SubSonic/Infrastructure/Builders/DbSqlQueryBuilder/DbSqlQueryBuilderQueryProvider.cs

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace SubSonic.Infrastructure.Builders
1414
using Logging;
1515
using Linq.Expressions;
1616
using System.Threading.Tasks;
17+
using SubSonic.Linq;
1718

1819
public partial class DbSqlQueryBuilder
1920
{
@@ -40,8 +41,8 @@ public TResult Execute<TResult>(Expression expression)
4041
throw new ArgumentNullException(nameof(expression));
4142
}
4243

43-
if (expression is DbExpression select)
44-
{
44+
if (expression is DbExpression dbExpression)
45+
{ // execution request is from the subsonic namespace
4546
using (SharedDbConnectionScope Scope = DbContext.ServiceProvider.GetService<SharedDbConnectionScope>())
4647
{
4748
CmdBehavior = typeof(TResult).IsEnumerable() ? CommandBehavior.Default : CommandBehavior.SingleRow;
@@ -51,9 +52,9 @@ public TResult Execute<TResult>(Expression expression)
5152
bool isEntityModel = DbContext.DbModel.IsEntityModelRegistered(elementType);
5253

5354
if (!isEntityModel ||
54-
DbContext.Current.ChangeTracking.Count(elementType, select) == 0)
55+
DbContext.Current.ChangeTracking.Count(elementType, dbExpression) == 0)
5556
{
56-
IDbQuery dbQuery = ToQuery(select);
57+
IDbQuery dbQuery = ToQuery(dbExpression);
5758

5859
try
5960
{
@@ -96,47 +97,61 @@ public TResult Execute<TResult>(Expression expression)
9697
}
9798
}
9899
}
99-
else if (expression is MethodCallExpression method)
100-
{ // Linq call is coming from System.Linq namespace directly.
101-
// expression needs to be rebuilt into something the DAL can use
102-
while (method.Arguments[0] is MethodCallExpression _method)
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++)
103107
{
104-
method = _method;
108+
if (call.Arguments[i] is DbSelectExpression select)
109+
{
110+
dbSelect = select;
111+
}
112+
else if (call.Arguments[i] is UnaryExpression unary)
113+
{
114+
where = BuildWhere(dbSelect.From, dbSelect.Where, dbSelect.Type, unary);
115+
}
105116
}
106117

107-
108-
109-
if (method.Arguments[0] is DbSelectExpression _select)
118+
if (call.Method.Name.In(nameof(Queryable.Single), nameof(Queryable.SingleOrDefault), nameof(Queryable.First), nameof(Queryable.FirstOrDefault)))
110119
{
111-
Expression where = null;
120+
object result = Execute<TResult>(BuildSelect(dbSelect, where));
112121

113-
for(int i = 1, n = method.Arguments.Count; i < n; i++)
122+
if (result is TResult matched)
114123
{
115-
where = BuildWhere(_select.From, where, _select.Type, method.Arguments[i]);
124+
return matched;
116125
}
117-
118-
if (method.Method.Name.Equals(nameof(Queryable.Count), StringComparison.CurrentCulture))
119-
{ // the method count has been called on the collection
120-
if (BuildSelect(_select, where) is DbSelectExpression __select)
121-
{
122-
return Execute<TResult>(DbExpression.DbSelectAggregate(__select, new[]
123-
{
124-
DbExpression.DbAggregate(typeof(TResult), AggregateType.Count, __select.Columns.First(x => x.Property.IsPrimaryKey).Expression)
125-
}));
126-
}
126+
else if (result is IEnumerable<TResult> enumerable)
127+
{
128+
return enumerable.Any() ? enumerable.ElementAt(0) : default(TResult);
129+
}
130+
#if NETSTANDARD2_0
131+
else if (call.Method.Name.Contains("Default"))
132+
#elif NETSTANDARD2_1
133+
else if (call.Method.Name.Contains("Default", StringComparison.CurrentCulture))
134+
#endif
135+
{
136+
return default(TResult);
127137
}
128138
else
129139
{
130-
if (method.Arguments.Count > 1)
131-
{
132-
return Execute<TResult>(BuildSelect(_select, where));
133-
}
134-
else
140+
throw Error.InvalidOperation($"Method {call.Method.Name} expects data.");
141+
}
142+
}
143+
else if (call.Method.Name.In(nameof(Queryable.Count)))
144+
{
145+
if (BuildSelect(dbSelect, where) is DbSelectExpression select)
146+
{
147+
return Execute<TResult>(DbExpression.DbSelectAggregate(select, new[]
135148
{
136-
return Execute<TResult>(_select);
137-
}
149+
DbExpression.DbAggregate(typeof(TResult), AggregateType.Count, select.Columns.First(x => x.Property.IsPrimaryKey).Expression)
150+
}));
138151
}
139152
}
153+
154+
throw Error.NotSupported(SubSonicErrorMessages.ExpressionNotSupported.Format(call.Method.Name));
140155
}
141156

142157
throw new NotSupportedException(expression.ToString());

SubSonic/Interfaces/Builders/IDbSqlQueryBuilder.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public interface ISubSonicQueryProvider
2929
Expression BuildSelect(Expression eSelect, IEnumerable<DbOrderByDeclaration> orderBy);
3030
Expression BuildSelect(Expression eSelect, IEnumerable<Expression> groupBy);
3131
Expression BuildSelect(Expression eSelect, DbExpressionType eType, IEnumerable<Expression> expressions);
32-
Expression BuildSelect<TEntity, TColumn>(Expression eSelect, Expression<Func<TEntity, TColumn>> selector);
3332
Expression BuildWhere(DbTableExpression table, Expression where, Type type, LambdaExpression predicate);
3433
Expression BuildWhereExists<TEntity>(DbTableExpression dbTableExpression, Type type, Expression<Func<TEntity, IQueryable>> query);
3534
Expression BuildWhereNotExists<TEntity>(DbTableExpression from, Type type, Expression<Func<TEntity, IQueryable>> query);

SubSonic/Linq/Expressions/DbSelectExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public static DbExpression DbSelect(object collection, Type type, DbTableExpress
137137
};
138138
}
139139

140-
public static DbExpression DbSelect(DbSelectExpression select, DbExpression where)
140+
public static DbExpression DbSelect(DbSelectExpression select, Expression where)
141141
{
142142
if (select is null)
143143
{

0 commit comments

Comments
 (0)