Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions AgileMapper.UnitTests.Orms.EfCore2/WhenCreatingProjections.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace AgileObjects.AgileMapper.UnitTests.Orms.EfCore2
{
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Common;
using Infrastructure;
Expand Down Expand Up @@ -49,5 +51,22 @@ public Task ShouldReuseACachedProjectionMapper()
mapper.RootMapperCountShouldBeOne();
});
}

[Fact]
public Task ShouldMapAQueryableAsAnEnumerable()
{
return RunTest(async (context, mapper) =>
{
await context.BoolItems.AddRangeAsync(new PublicBool { Value = true }, new PublicBool { Value = false });
await context.SaveChangesAsync();

var result = mapper
.Map(context.BoolItems.Where(bi => bi.Value))
.ToANew<List<PublicBoolDto>>();

result.ShouldNotBeNull();
result.ShouldHaveSingleItem().Value.ShouldBeTrue();
});
}
}
}
33 changes: 33 additions & 0 deletions AgileMapper.UnitTests/WhenMappingToMetaMembers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using AgileMapper.Extensions;
using Common;
using TestClasses;
Expand Down Expand Up @@ -144,6 +145,18 @@ public void ShouldPopulateAnEmptyHasEnumerableMemberNameMemberWithFalse()
result.HasValue.ShouldBeFalse();
}

[Fact]
public void ShouldPopulateAHasQueryableMemberNameMember()
{
var source = new PublicField<IQueryable<Address>>
{
Value = new[] { new Address { Line1 = "Queryable?!" } }.AsQueryable()
};
var result = Mapper.Map(source).ToANew<PublicHasValue<Address[]>>();

result.HasValue.ShouldBeTrue();
}

[Fact]
public void ShouldPopulateAnIntHasMemberNameMember()
{
Expand Down Expand Up @@ -266,6 +279,16 @@ public void ShouldPopulateAnEmptyFirstArrayMemberNameMemberToNull()
result.FirstItem.ShouldBeNull();
}

[Fact]
public void ShouldPopulateAnEmptyFirstQueryableMemberNameMemberToNull()
{
var source = new { Items = Enumerable<Address>.EmptyArray.AsQueryable() };
var result = Mapper.Map(source).ToANew<PublicFirstItem<Address, IList<Address>>>();

result.Items.ShouldBeEmpty();
result.FirstItem.ShouldBeNull();
}

[Fact]
public void ShouldPopulateAFirstListMemberNameMember()
{
Expand Down Expand Up @@ -545,6 +568,16 @@ public void ShouldPopulateAListLongCountMember()
result.ValueCount.ShouldBe(5L);
}

[Fact]
public void ShouldPopulateAQueryableLongCountMember()
{
var source = new { Values = new[] { "1", "2", "3", "4" }.AsQueryable() };
var result = Mapper.Map(source).ToANew<PublicValuesCount<long, Collection<string>>>();

result.Values.Count.ShouldBe(4);
result.ValueCount.ShouldBe(4L);
}

[Fact]
public void ShouldNotPopulateANonNumericCountMember()
{
Expand Down
40 changes: 15 additions & 25 deletions AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
#if NET35
using Microsoft.Scripting.Ast;
#else
using System.Linq.Expressions;
#endif
using Extensions;
using Extensions.Internal;
using Members;
Expand All @@ -11,11 +16,6 @@
using ObjectPopulation.Enumerables;
using ReadableExpressions.Extensions;
using TypeConversion;
#if NET35
using Microsoft.Scripting.Ast;
#else
using System.Linq.Expressions;
#endif
using static System.StringComparison;

internal struct MetaMemberDataSourceFinder : IDataSourceFinder
Expand Down Expand Up @@ -310,8 +310,10 @@ protected static Expression GetLinqMethodCall(
Expression enumerable,
EnumerableTypeHelper helper)
{
var enumerableType = helper.IsQueryableInterface ? typeof(Queryable) : typeof(Enumerable);

return Expression.Call(
typeof(Enumerable)
enumerableType
.GetPublicStaticMethod(methodName, parameterCount: 1)
.MakeGenericMethod(helper.ElementType),
enumerable);
Expand Down Expand Up @@ -357,26 +359,16 @@ public override Expression GetAccess(Expression parentInstance)

private Expression GetHasCheck(Expression queriedMemberAccess)
{
if (QueriedMember.IsEnumerable)
{
return GetHasEnumerableCheck(queriedMemberAccess);
}

var queriedMemberNotDefault = queriedMemberAccess.GetIsNotDefaultComparison();

if (QueriedMember.IsSimple)
{
return queriedMemberNotDefault;
}

return queriedMemberNotDefault;
return QueriedMember.IsEnumerable
? GetHasEnumerableCheck(queriedMemberAccess)
: queriedMemberAccess.GetIsNotDefaultComparison();
}

private static Expression GetHasEnumerableCheck(Expression enumerableAccess)
{
var helper = new EnumerableTypeHelper(enumerableAccess.Type);

return helper.IsEnumerableInterface
return helper.IsEnumerableOrQueryable
? GetLinqMethodCall(nameof(Enumerable.Any), enumerableAccess, helper)
: GetEnumerableCountCheck(enumerableAccess, helper);
}
Expand Down Expand Up @@ -470,14 +462,12 @@ private Expression GetCondition(Expression enumerableAccess, EnumerableTypeHelpe

private Expression GetOrderedEnumerableAccess(Expression enumerableAccess, EnumerableTypeHelper helper)
{
var elementType = _sourceMember.Type;

if (!elementType.IsComplex())
if (!_sourceMember.Type.IsComplex())
{
return GetLinqMethodCall(LinqSelectionMethodName, enumerableAccess, helper);
}

var orderMember = MapperData.GetOrderMember(elementType);
var orderMember = MapperData.GetOrderMember(_sourceMember.Type);

if (orderMember == null)
{
Expand Down Expand Up @@ -559,7 +549,7 @@ public override Expression GetAccess(Expression enumerableAccess)
{
var helper = new EnumerableTypeHelper(enumerableAccess.Type);

var count = helper.IsEnumerableInterface
var count = helper.IsEnumerableOrQueryable
? GetLinqMethodCall(nameof(Enumerable.Count), enumerableAccess, helper)
: helper.GetCountFor(enumerableAccess, MapperData.TargetMember.Type.GetNonNullableType());

Expand Down
2 changes: 1 addition & 1 deletion AgileMapper/Extensions/Internal/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ public static Expression GetEmptyInstanceCreation(
typeHelper = new EnumerableTypeHelper(enumerableType, elementType);
}

if (typeHelper.IsEnumerableInterface)
if (typeHelper.IsEnumerableOrQueryable)
{
return Expression.Field(null, typeof(Enumerable<>).MakeGenericType(elementType), "Empty");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,9 @@ private Expression GetNullTargetConstruction()
{
var nullTargetVariableType = GetNullTargetVariableType();

if (SourceTypeHelper.IsEnumerableInterface)
if (SourceTypeHelper.IsEnumerableOrQueryable)
{
// Can't use a capacity constructor as unable to count source elements:
// Don't use a capacity constructor as source count not readily available:
return Expression.New(nullTargetVariableType);
}

Expand Down
12 changes: 10 additions & 2 deletions AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Extensions.Internal;
using Members;
using NetStandardPolyfills;
Expand All @@ -24,6 +25,7 @@ internal class EnumerableTypeHelper
private Type _readOnlyCollectionType;
private Type _collectionInterfaceType;
private Type _enumerableInterfaceType;
private Type _queryableInterfaceType;
#if FEATURE_ISET
private Type _setInterfaceType;
#endif
Expand All @@ -49,7 +51,11 @@ public bool IsDictionary

public bool IsReadOnlyCollection => EnumerableType == ReadOnlyCollectionType;

public bool IsEnumerableInterface => EnumerableType == EnumerableInterfaceType;
private bool IsEnumerableInterface => EnumerableType == EnumerableInterfaceType;

public bool IsQueryableInterface => EnumerableType == QueryableInterfaceType;

public bool IsEnumerableOrQueryable => IsEnumerableInterface || IsQueryableInterface;

public bool HasCollectionInterface => EnumerableType.IsAssignableTo(CollectionInterfaceType);

Expand All @@ -61,7 +67,7 @@ public bool IsDictionary
public bool IsReadOnly => IsArray || IsReadOnlyCollection;

public bool IsDeclaredReadOnly
=> IsReadOnly || IsEnumerableInterface || IsReadOnlyCollectionInterface();
=> IsReadOnly || IsEnumerableOrQueryable || IsReadOnlyCollectionInterface();

public bool CouldBeReadOnly()
{
Expand Down Expand Up @@ -111,6 +117,8 @@ private bool IsReadOnlyCollectionInterface()

public Type EnumerableInterfaceType => GetEnumerableType(ref _enumerableInterfaceType, typeof(IEnumerable<>));

public Type QueryableInterfaceType => GetEnumerableType(ref _queryableInterfaceType, typeof(IQueryable<>));

#if FEATURE_ISET
private Type SetInterfaceType => GetEnumerableType(ref _setInterfaceType, typeof(ISet<>));
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ protected SourceEnumerableAdapterBase(EnumerablePopulationBuilder builder)
public virtual Expression GetSourceValues() => Builder.MapperData.SourceObject;

public virtual bool UseReadOnlyTargetWrapper =>
TargetTypeHelper.IsReadOnly && !SourceTypeHelper.IsEnumerableInterface;
TargetTypeHelper.IsReadOnly && !SourceTypeHelper.IsEnumerableOrQueryable;
}
}
7 changes: 4 additions & 3 deletions AgileMapper/Queryables/QueryProjectionExpressionFactory.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
namespace AgileObjects.AgileMapper.Queryables
{
using System.Collections.Generic;
using Extensions.Internal;
using ObjectPopulation;
#if NET35
using Microsoft.Scripting.Ast;
#else
using System.Linq.Expressions;
#endif
using Extensions.Internal;
using ObjectPopulation;

internal class QueryProjectionExpressionFactory : MappingExpressionFactoryBase
{
Expand All @@ -18,7 +18,8 @@ public override bool IsFor(IObjectMappingData mappingData)
var mapperData = mappingData.MapperData;

return mapperData.IsRoot &&
mapperData.TargetMember.IsEnumerable &&
mapperData.TargetMember.IsEnumerable &&
(mappingData.MappingContext.RuleSet.Name == Constants.Project) &&
mapperData.SourceType.IsQueryable();
}

Expand Down