Skip to content

Commit

Permalink
Fix enumerator not disposed when arbitrary projection is enumerated (#…
Browse files Browse the repository at this point in the history
…132)

* Add failing test for should dispose enumerator

* Dispose enumerator when arbitrary projection is enumerated

Let's `foreach` the enumerable, which automatically disposes enumerator.

* Fix typos
  • Loading branch information
acjh authored Feb 22, 2022
1 parent e16bb6f commit 4e85a57
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,20 @@ public TResult Execute<TResult>(Expression expression)
destResult = (IQueryable<TDestination>)GetMapExpressions(queryExpressions).Aggregate(sourceResult, Select);
}
// case #2: query is arbitrary ("manual") projection
// exaple: users.UseAsDataSource().For<UserDto>().Select(user => user.Age).ToList()
// example: users.UseAsDataSource().For<UserDto>().Select(user => user.Age).ToList()
// in case an arbitrary select-statement is enumerated, we do not need to map the expression at all
// and cann safely return it
// and can safely return it
else if (IsProjection(resultType, sourceExpression))
{
var sourceResult = _dataSource.Provider.CreateQuery(sourceExpression);
var enumerator = sourceResult.GetEnumerator();
var elementType = ElementTypeHelper.GetElementType(typeof(TResult));
var constructorInfo = typeof(List<>).MakeGenericType(elementType).GetDeclaredConstructor(new Type[0]);
if (constructorInfo != null)
{
var listInstance = (IList)constructorInfo.Invoke(null);
while (enumerator.MoveNext())
foreach (var element in sourceResult)
{
listInstance.Add(enumerator.Current);
listInstance.Add(element);
}
destResult = listInstance;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
Expand Down Expand Up @@ -622,6 +623,23 @@ public void Shoud_convert_type_changes()
// //result.Any(s => s.DestValue > 6).ShouldBeTrue();
//}

[Fact]
public void Should_dispose_enumerator_when_arbitrary_projection_is_enumerated()
{
// Arrange
var mapper = SetupAutoMapper();
var source = new NotSingleQueryingEnumerable<Detail>();

// Act
source.AsQueryable()
.UseAsDataSource(mapper).For<DetailDto>()
.Select(m => m.Name)
.ToList();

// Assert
NotRelationalDataReader.Instance.ShouldBeNull();
}

private static IMapper SetupAutoMapper()
{
var config = new MapperConfiguration(cfg =>
Expand Down Expand Up @@ -825,6 +843,96 @@ public class ResourceDto
public string Title { get; set; }
public bool HasEditPermission { get; set; }
}

public class NotRelationalDataReader : IDisposable
{
public static NotRelationalDataReader Instance { get; set; }

public NotRelationalDataReader()
{
Instance = this;
}

public void Dispose()
{
Instance = null;
}
}

/// <remarks>
/// Based on <see href="https://github.com/dotnet/efcore/blob/08e5ebd/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs">Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable&lt;T&gt;</see>
/// for <see cref="Enumerator.MoveNext"/> and <see cref="Enumerator.Dispose"/>.
/// </remarks>
public class NotSingleQueryingEnumerable<T> : IQueryable<T>, IQueryProvider
{
public Type ElementType => throw new NotImplementedException();

public Expression Expression => Expression.Constant(this);

public IQueryProvider Provider => this;

public IQueryable CreateQuery(Expression expression)
{
return this;
}

public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
throw new NotImplementedException();
}

public object Execute(Expression expression)
{
throw new NotImplementedException();
}

public TResult Execute<TResult>(Expression expression)
{
throw new NotImplementedException();
}

public IEnumerator<T> GetEnumerator()
{
throw new NotImplementedException();
}

IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator();
}

private sealed class Enumerator : IEnumerator<T>
{
private NotRelationalDataReader _dataReader;

public T Current => throw new NotImplementedException();

object IEnumerator.Current => throw new NotImplementedException();

public void Dispose()
{
if (_dataReader is not null)
{
_dataReader.Dispose();
_dataReader = null;
}
}

public bool MoveNext()
{
if (_dataReader == null)
{
_dataReader = new NotRelationalDataReader();
}
return false;
}

public void Reset()
{
throw new NotImplementedException();
}
}
}
}


0 comments on commit 4e85a57

Please sign in to comment.