Skip to content

Commit 1ce3bf8

Browse files
committed
ensure collections were loading data on enumeration
1 parent 163af66 commit 1ce3bf8

File tree

12 files changed

+213
-99
lines changed

12 files changed

+213
-99
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using NUnit.Framework;
2+
using SubSonic.Extensions.Test.Models;
3+
using SubSonic.Tests.DAL.SUT;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using FluentAssertions;
7+
8+
namespace SubSonic.Tests.DAL.Collections
9+
{
10+
using Linq;
11+
using Extensions.Test;
12+
13+
[TestFixture]
14+
public class CollectionTests
15+
: BaseTestFixture
16+
{
17+
public override void SetupTestFixture()
18+
{
19+
base.SetupTestFixture();
20+
21+
string
22+
people_greater_than = @"SELECT [T1].[ID], [T1].[FirstName], [T1].[MiddleInitial], [T1].[FamilyName], [T1].[FullName]
23+
FROM [dbo].[Person] AS [T1]
24+
WHERE ([T1].[ID] > @id_1)";
25+
26+
Context.Database.Instance.AddCommandBehavior(people_greater_than.Format("T1"), cmd => People.Where(x => x.ID > cmd.Parameters["@id_1"].GetValue<int>()).ToDataTable());
27+
}
28+
29+
[Test]
30+
public void CollectionShouldLoadAndCastToArray()
31+
{
32+
Person[] people = Context.People.Where(x => x.ID > 2).ToArray();
33+
34+
people.Length.Should().BeGreaterThan(0);
35+
}
36+
37+
[Test]
38+
public void CollectionShouldLoadAndCastToList()
39+
{
40+
List<Person> people = Context.People.Where(x => x.ID > 2).ToList();
41+
42+
people.Count.Should().BeGreaterThan(0);
43+
}
44+
}
45+
}

SubSonic.Tests/DAL/SUT/DbTestCase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public IEntityProxy FindByID(params object[] keyData)
7878

7979
public IEnumerable FetchAll()
8080
{
81-
SysLinq.IQueryable<TModel> data = null;
81+
IQueryable data = null;
8282

8383
if (DataSet is ISubSonicCollection<TModel> dataSet)
8484
{

SubSonic/Data/ChangeTracking/ChangeTrackerCollection.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,25 @@ public bool SaveChanges(out string feedback)
152152

153153
public TResult Where<TResult>(Type elementKey, System.Linq.IQueryProvider provider, Expression expression)
154154
{
155-
return collection[elementKey].Where<TResult>(provider, expression);
155+
object result = collection[elementKey].Where(provider, expression);
156+
157+
if (result is TResult success)
158+
{
159+
return success;
160+
}
161+
else if (result is IEnumerable<TResult> workneeded)
162+
{
163+
return workneeded.FirstOrDefault();
164+
}
165+
else
166+
{
167+
return default(TResult);
168+
}
169+
}
170+
171+
public IEnumerable Where(Type elementKey, IQueryProvider provider, Expression expression)
172+
{
173+
return collection[elementKey].Where(provider, expression);
156174
}
157175

158176
private IEnumerable<KeyValuePair<Type, IEnumerable<IEntityProxy>>> BuildEnumeration()

SubSonic/Data/ChangeTracking/ChangeTrackerElement.cs

Lines changed: 5 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -229,45 +229,6 @@ public override bool SaveChanges(DbQueryType queryType, IEnumerable<IEntityProxy
229229
);
230230
}
231231

232-
233-
//{
234-
// if (data.ElementAt(i) is IEntityProxy<TEntity> entity)
235-
// {
236-
// if (queryType == DbQueryType.Delete)
237-
// {
238-
// Remove(entity);
239-
240-
// continue;
241-
// }
242-
243-
// if (result.Length == 0)
244-
// {
245-
// flag(entity);
246-
247-
// continue;
248-
// }
249-
250-
// IEntityProxy<TEntity> @object = result[i];
251-
252-
// if (queryType == DbQueryType.Update)
253-
// {
254-
// @object = result.Single(x =>
255-
// x.KeyData.SequenceEqual(entity.KeyData));
256-
// }
257-
258-
// if(queryType == DbQueryType.Insert)
259-
// {
260-
// entity.SetKeyData(@object.KeyData);
261-
// }
262-
263-
// if(queryType.In(DbQueryType.Insert, DbQueryType.Update))
264-
// {
265-
// entity.SetDbComputedProperties(@object);
266-
// flag(entity);
267-
// }
268-
// }
269-
//}
270-
271232
success = true;
272233
}
273234
finally { }
@@ -276,11 +237,11 @@ public override bool SaveChanges(DbQueryType queryType, IEnumerable<IEntityProxy
276237
}
277238
}
278239

279-
public override TResult Where<TResult>(IQueryProvider provider, Expression expression)
240+
public override IEnumerable Where(IQueryProvider provider, Expression query)
280241
{
281242
if (Cache is ObservableCollection<IEntityProxy<TEntity>> cache)
282243
{
283-
if (expression is DbSelectExpression select)
244+
if (query is DbSelectExpression select)
284245
{
285246
IEnumerable<TEntity> results = cache
286247
.Where(x => x.IsNew == false && x.IsDirty == false)
@@ -294,17 +255,10 @@ public override TResult Where<TResult>(IQueryProvider provider, Expression expre
294255
}
295256
}
296257

297-
if (typeof(TResult).IsEnumerable())
298-
{
299-
return (TResult)Activator.CreateInstance(typeof(SubSonicCollection<>).MakeGenericType(Key),
258+
return (IEnumerable)Activator.CreateInstance(typeof(SubSonicCollection<>).MakeGenericType(Key),
300259
provider,
301-
expression,
260+
query,
302261
results);
303-
}
304-
else
305-
{
306-
return results.SingleOrDefault<TEntity, TResult>();
307-
}
308262
}
309263
}
310264

@@ -351,7 +305,7 @@ public void Clear()
351305

352306
public abstract bool SaveChanges(DbQueryType queryType, IEnumerable<IEntityProxy> data, out string error);
353307

354-
public abstract TResult Where<TResult>(System.Linq.IQueryProvider provider, Expression expression);
308+
public abstract IEnumerable Where(IQueryProvider provider, Expression expression);
355309

356310
public IEnumerator GetEnumerator()
357311
{

SubSonic/Infrastructure/Builders/DbSqlQueryBuilder/DbSqlQueryBuilderQueryProvider.cs

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public TResult Execute<TResult>(Expression expression)
4141
throw new ArgumentNullException(nameof(expression));
4242
}
4343

44-
if (expression is DbExpression dbExpression)
44+
if (expression is DbExpression query)
4545
{ // execution request is from the subsonic namespace
4646
using (SharedDbConnectionScope Scope = DbContext.ServiceProvider.GetService<SharedDbConnectionScope>())
4747
{
@@ -52,27 +52,28 @@ public TResult Execute<TResult>(Expression expression)
5252
bool isEntityModel = DbContext.DbModel.IsEntityModelRegistered(elementType);
5353

5454
if (!isEntityModel ||
55-
DbContext.Current.ChangeTracking.Count(elementType, dbExpression) == 0)
55+
DbContext.Current.ChangeTracking.Count(elementType, query) == 0)
5656
{
57-
IDbQuery dbQuery = ToQuery(dbExpression);
57+
IDbQuery dbQuery = ToQuery(query);
5858

5959
try
6060
{
6161
Scope.Connection.Open();
6262

63-
DbDataReader reader = Scope.Database.ExecuteReader(dbQuery);
64-
65-
while (reader.Read())
63+
using (DbDataReader reader = Scope.Database.ExecuteReader(dbQuery))
6664
{
67-
if (isEntityModel)
68-
{
69-
DbContext.Current.ChangeTracking.Add(elementType, reader.ActivateAndLoadInstanceOf(elementType));
70-
}
71-
else
65+
while (reader.Read())
7266
{
73-
if (CmdBehavior == CommandBehavior.SingleRow)
67+
if (isEntityModel)
68+
{
69+
DbContext.Current.ChangeTracking.Add(elementType, reader.ActivateAndLoadInstanceOf(elementType));
70+
}
71+
else
7472
{
75-
return Scalar<TResult>(reader);
73+
if (CmdBehavior == CommandBehavior.SingleRow)
74+
{
75+
return Scalar<TResult>(reader);
76+
}
7677
}
7778
}
7879
}
@@ -87,7 +88,7 @@ public TResult Execute<TResult>(Expression expression)
8788

8889
if (isEntityModel)
8990
{
90-
return DbContext.Current.ChangeTracking.Where<TResult>(elementType, this, expression);
91+
return DbContext.Current.ChangeTracking.Where<TResult>(elementType, this, query);
9192
}
9293
else
9394
{
@@ -177,19 +178,53 @@ public object Execute(Expression expression)
177178
throw new ArgumentNullException(nameof(expression));
178179
}
179180

180-
using (SharedDbConnectionScope Scope = DbContext.ServiceProvider.GetService<SharedDbConnectionScope>())
181+
if (expression is DbExpression query)
181182
{
182-
IDbQuery dbQuery = ToQuery(expression);
183-
184-
try
185-
{
186-
return Scope.Database.ExecuteReader(dbQuery);
187-
}
188-
finally
183+
using (SharedDbConnectionScope Scope = DbContext.ServiceProvider.GetService<SharedDbConnectionScope>())
189184
{
190-
dbQuery.CleanUpParameters();
185+
IDbQuery dbQuery = ToQuery(query);
186+
187+
try
188+
{
189+
Type elementType = query.Type.GetQualifiedType();
190+
191+
bool isEntityModel = DbContext.DbModel.IsEntityModelRegistered(elementType);
192+
193+
using (DbDataReader reader = Scope.Database.ExecuteReader(dbQuery))
194+
{
195+
if (reader.HasRows)
196+
{
197+
while (reader.Read())
198+
{
199+
if (isEntityModel)
200+
{
201+
DbContext.Current.ChangeTracking.Add(elementType, reader.ActivateAndLoadInstanceOf(elementType));
202+
}
203+
}
204+
}
205+
}
206+
207+
if (isEntityModel)
208+
{
209+
return DbContext.Current.ChangeTracking.Where(elementType, this, query);
210+
}
211+
else
212+
{
213+
logger.LogDebug(SubSonicErrorMessages.NoDataAvailable);
214+
215+
return Array.Empty<object>();
216+
}
217+
}
218+
finally
219+
{
220+
dbQuery.CleanUpParameters();
221+
}
191222
}
192223
}
224+
else
225+
{
226+
throw Error.NotSupported($"{expression} Not Supported");
227+
}
193228
}
194229
}
195230
}

SubSonic/Infrastructure/Database/DbDatabase.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,16 @@ public DbDataReader ExecuteReader(IDbQuery queryObject)
262262
{
263263
logger.LogTrace(queryObject.Sql);
264264

265-
return cmd.ExecuteReader(queryObject.Behavior);
265+
try
266+
{
267+
cmd.Connection.Open();
268+
269+
return cmd.ExecuteReader(queryObject.Behavior);
270+
}
271+
finally
272+
{
273+
cmd.Connection.Close();
274+
}
266275
}
267276
}
268277

SubSonic/Infrastructure/Database/SubSonicQueryable.cs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,16 @@ public void CopyTo(TElement[] elements, int startAt)
106106
throw new NotSupportedException();
107107
}
108108
}
109+
109110
IEnumerator<TElement> IEnumerable<TElement>.GetEnumerator()
110111
{
111112
if (TableData is ICollection<TElement> data)
112113
{
114+
if (!IsLoaded)
115+
{
116+
Load();
117+
}
118+
113119
return data.GetEnumerator();
114120
}
115121
else
@@ -118,19 +124,14 @@ IEnumerator<TElement> IEnumerable<TElement>.GetEnumerator()
118124
}
119125
}
120126
#endregion
121-
122-
public IQueryable<TElement> Load()
123-
{
124-
AddRange(Linq.SubSonicQueryable.Load(this));
125-
126-
return this;
127-
}
128127
}
129128

130129
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1010:Collections should implement generic interface", Justification = "Generic Class that inherits from this one addresses the generic interface")]
131130
public class SubSonicCollection
132131
: ISubSonicCollection
133132
{
133+
protected bool IsLoaded { get; set; }
134+
134135
public SubSonicCollection(Type elementType)
135136
{
136137
ElementType = elementType ?? throw new ArgumentNullException(nameof(elementType));
@@ -158,23 +159,41 @@ public SubSonicCollection(Type elementType, IQueryProvider provider, Expression
158159
: this(elementType, provider, expression)
159160
{
160161
TableData = (IEnumerable)Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(elementType), elements);
162+
IsLoaded = true;
161163
}
162164

163165
protected IDbEntityModel Model { get; }
164166

165-
protected IEnumerable TableData { get; }
167+
protected IEnumerable TableData { get; private set; }
166168

167169
public Type ElementType { get; }
168170

169171
public Expression Expression { get; }
170172

171173
public IQueryProvider Provider { get; }
172174

175+
public virtual IQueryable Load()
176+
{
177+
if (Provider.Execute(Expression) is IEnumerable elements)
178+
{
179+
TableData = (IEnumerable)Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(ElementType), elements);
180+
181+
IsLoaded = true;
182+
}
183+
184+
return this;
185+
}
186+
173187
#region ICollection<> Implementation
174188
public int Count => (int)TableData.GetType().GetProperty(nameof(Count)).GetValue(TableData);
175189
public bool IsReadOnly => false;
176190
public IEnumerator GetEnumerator()
177191
{
192+
if (!IsLoaded)
193+
{
194+
Load();
195+
}
196+
178197
return TableData.GetEnumerator();
179198
}
180199
#endregion

0 commit comments

Comments
 (0)