Skip to content

Commit 63ad28e

Browse files
authored
Adding coverlet threshold to builds. (#197)
1 parent 75323c7 commit 63ad28e

File tree

6 files changed

+221
-8
lines changed

6 files changed

+221
-8
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
run: dotnet nuget add source https://www.myget.org/F/automapperdev/api/v3/index.json -n automappermyget
2323

2424
- name: Test
25-
run: dotnet test --configuration Release --verbosity normal
25+
run: dotnet test --configuration Release --verbosity normal /p:CollectCoverage=true /p:Threshold=94 /p:ThresholdType=line /p:ThresholdStat=Average /p:CoverletOutputFormat=opencover /p:CoverletOutput=./TestResults/ /p:ExcludeByAttribute="GeneratedCodeAttribute"
2626

2727
- name: Pack and push
2828
env:

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
fetch-depth: 0
1818

1919
- name: Test
20-
run: dotnet test --configuration Release --verbosity normal
20+
run: dotnet test --configuration Release --verbosity normal /p:CollectCoverage=true /p:Threshold=94 /p:ThresholdType=line /p:ThresholdStat=Average /p:CoverletOutputFormat=opencover /p:CoverletOutput=./TestResults/ /p:ExcludeByAttribute="GeneratedCodeAttribute"
2121

2222
- name: Pack and push
2323
env:

README.md

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ The methods below map the DTO query expresions to the equivalent data query expr
6969
return mapper.Map<TDataResult, TModelResult>(mappedQueryFunc(query));
7070
}
7171

72-
//This version compiles the queryable expression.
72+
//This example compiles the queryable expression.
7373
internal static IQueryable<TModel> GetQuery1<TModel, TData>(this IQueryable<TData> query,
7474
IMapper mapper,
7575
Expression<Func<TModel, bool>> filter = null,
@@ -87,7 +87,7 @@ The methods below map the DTO query expresions to the equivalent data query expr
8787
Expression<Func<TModel, object>>[] GetExpansions() => expansions?.ToArray() ?? [];
8888
}
8989

90-
//This version updates IQueryable<TData>.Expression with the mapped queryable expression parameter.
90+
//This example updates IQueryable<TData>.Expression with the mapped queryable expression argument.
9191
internal static IQueryable<TModel> GetQuery2<TModel, TData>(this IQueryable<TData> query,
9292
IMapper mapper,
9393
Expression<Func<TModel, bool>> filter = null,
@@ -128,3 +128,68 @@ The methods below map the DTO query expresions to the equivalent data query expr
128128
}
129129
}
130130
```
131+
132+
## Known Issues
133+
Mapping a single type in the source expression to multiple types in the destination expression is not supported e.g.
134+
```c#
135+
```
136+
[Fact]
137+
public void Can_map_if_source_type_targets_multiple_destination_types_in_the_same_expression()
138+
{
139+
var mapper = ConfigurationHelper.GetMapperConfiguration(cfg =>
140+
{
141+
cfg.CreateMap<SourceType, TargetType>().ReverseMap();
142+
cfg.CreateMap<SourceChildType, TargetChildType>().ReverseMap();
143+
144+
// Same source type can map to different target types. This seems unsupported currently.
145+
cfg.CreateMap<SourceListItemType, TargetListItemType>().ReverseMap();
146+
cfg.CreateMap<SourceListItemType, TargetChildListItemType>().ReverseMap();
147+
148+
}).CreateMapper();
149+
150+
Expression<Func<SourceType, bool>> sourcesWithListItemsExpr = src => src.Id != 0 && src.ItemList.Any() && src.Child.ItemList.Any(); // Sources with non-empty ItemList
151+
Expression<Func<TargetType, bool>> target1sWithListItemsExpr = mapper.MapExpression<Expression<Func<TargetType, bool>>>(sourcesWithListItemsExpr);
152+
}
153+
154+
private class SourceChildType
155+
{
156+
public int Id { get; set; }
157+
public IEnumerable<SourceListItemType> ItemList { get; set; } // Uses same type (SourceListItemType) for its itemlist as SourceType
158+
}
159+
160+
private class SourceType
161+
{
162+
public int Id { get; set; }
163+
public SourceChildType Child { set; get; }
164+
public IEnumerable<SourceListItemType> ItemList { get; set; }
165+
}
166+
167+
private class SourceListItemType
168+
{
169+
public int Id { get; set; }
170+
}
171+
172+
private class TargetChildType
173+
{
174+
public virtual int Id { get; set; }
175+
public virtual ICollection<TargetChildListItemType> ItemList { get; set; } = [];
176+
}
177+
178+
private class TargetChildListItemType
179+
{
180+
public virtual int Id { get; set; }
181+
}
182+
183+
private class TargetType
184+
{
185+
public virtual int Id { get; set; }
186+
187+
public virtual TargetChildType Child { get; set; }
188+
189+
public virtual ICollection<TargetListItemType> ItemList { get; set; } = [];
190+
}
191+
192+
private class TargetListItemType
193+
{
194+
public virtual int Id { get; set; }
195+
}

src/AutoMapper.Extensions.ExpressionMapping/XpressionMapperVisitor.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ protected override Expression VisitLambda<T>(Expression<T> node)
183183

184184
protected override Expression VisitNew(NewExpression node)
185185
{
186-
if (this.TypeMappings.TryGetValue(node.Type, out Type newType))
186+
Type newType = this.TypeMappingsManager.ReplaceType(node.Type);
187+
if (newType != node.Type && !IsAnonymousType(node.Type))
187188
{
188189
return Expression.New(newType);
189190
}
@@ -217,7 +218,8 @@ private static bool IsAnonymousType(Type type)
217218

218219
protected override Expression VisitMemberInit(MemberInitExpression node)
219220
{
220-
if (this.TypeMappings.TryGetValue(node.Type, out Type newType))
221+
Type newType = this.TypeMappingsManager.ReplaceType(node.Type);
222+
if (newType != node.Type && !IsAnonymousType(node.Type))
221223
{
222224
var typeMap = ConfigurationProvider.CheckIfTypeMapExists(sourceType: newType, destinationType: node.Type);
223225
//The destination becomes the source because to map a source expression to a destination expression,
@@ -474,7 +476,8 @@ Expression DoVisitConditional(Expression test, Expression ifTrue, Expression ifF
474476

475477
protected override Expression VisitTypeBinary(TypeBinaryExpression node)
476478
{
477-
if (this.TypeMappings.TryGetValue(node.TypeOperand, out Type mappedType))
479+
Type mappedType = this.TypeMappingsManager.ReplaceType(node.TypeOperand);
480+
if (mappedType != node.TypeOperand)
478481
return MapTypeBinary(this.Visit(node.Expression));
479482

480483
return base.VisitTypeBinary(node);
@@ -498,7 +501,8 @@ protected override Expression VisitUnary(UnaryExpression node)
498501

499502
Expression DoVisitUnary(Expression updated)
500503
{
501-
if (this.TypeMappings.TryGetValue(node.Type, out Type mappedType))
504+
Type mappedType = this.TypeMappingsManager.ReplaceType(node.Type);
505+
if (mappedType != node.Type)
502506
return Expression.MakeUnary
503507
(
504508
node.NodeType,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using Xunit;
6+
7+
namespace AutoMapper.Extensions.ExpressionMapping.UnitTests
8+
{
9+
public class CanMapIfASourceTypeTargetsMultipleDestinationTypesInTheSameExpression
10+
{
11+
#pragma warning disable xUnit1004 // Test methods should not be skipped
12+
[Fact(Skip = "This test is currently skipped due to unsupported scenario.")]
13+
#pragma warning restore xUnit1004 // Test methods should not be skipped
14+
public void Can_map_if_source_type_targets_multiple_destination_types_in_the_same_expression()
15+
{
16+
// Arrange
17+
var mapper = ConfigurationHelper.GetMapperConfiguration(cfg =>
18+
{
19+
cfg.CreateMap<SourceType, TargetType>().ReverseMap();
20+
cfg.CreateMap<SourceChildType, TargetChildType>().ReverseMap();
21+
22+
// Same source type can map to different target types. This seems unsupported currently.
23+
cfg.CreateMap<SourceListItemType, TargetListItemType>().ReverseMap();
24+
cfg.CreateMap<SourceListItemType, TargetChildListItemType>().ReverseMap();
25+
26+
}).CreateMapper();
27+
Expression<Func<SourceType, bool>> sourcesWithListItemsExpr = src => src.Id != 0 && src.ItemList.Any() && src.Child.ItemList.Any(); // Sources with non-empty ItemList
28+
29+
// Act
30+
Expression<Func<TargetType, bool>> target1sWithListItemsExpr = mapper.MapExpression<Expression<Func<TargetType, bool>>>(sourcesWithListItemsExpr);
31+
32+
// Assert
33+
Assert.NotNull(target1sWithListItemsExpr);
34+
}
35+
36+
#pragma warning disable xUnit1004 // Test methods should not be skipped
37+
[Fact(Skip = "This test is currently skipped due to unsupported scenario.")]
38+
#pragma warning restore xUnit1004 // Test methods should not be skipped
39+
public void Can_map_if_source_type_targets_multiple_destination_types_in_the_same_expression_including_nested_parameters()
40+
{
41+
// Arrange
42+
var mapper = ConfigurationHelper.GetMapperConfiguration(cfg =>
43+
{
44+
cfg.CreateMap<SourceType, TargetType>().ReverseMap();
45+
cfg.CreateMap<SourceChildType, TargetChildType>().ReverseMap();
46+
47+
// Same source type can map to different target types. This seems unsupported currently.
48+
cfg.CreateMap<SourceListItemType, TargetListItemType>().ReverseMap();
49+
cfg.CreateMap<SourceListItemType, TargetChildListItemType>().ReverseMap();
50+
51+
}).CreateMapper();
52+
Expression<Func<SourceType, bool>> sourcesWithListItemsExpr = src => src.Id != 0 && src.ItemList.FirstOrDefault(i => i.Id == 1) == null && src.Child.ItemList.FirstOrDefault(i => i.Id == 1) == null; // Sources with non-empty ItemList
53+
54+
// Act
55+
Expression<Func<TargetType, bool>> target1sWithListItemsExpr = mapper.MapExpression<Expression<Func<TargetType, bool>>>(sourcesWithListItemsExpr);
56+
57+
//Assert
58+
Assert.NotNull(target1sWithListItemsExpr);
59+
}
60+
61+
private class SourceChildType
62+
{
63+
public int Id { get; set; }
64+
public IEnumerable<SourceListItemType> ItemList { get; set; } // Uses same type (SourceListItemType) for its itemlist as SourceType
65+
}
66+
67+
private class SourceType
68+
{
69+
public int Id { get; set; }
70+
public SourceChildType Child { set; get; }
71+
public IEnumerable<SourceListItemType> ItemList { get; set; }
72+
}
73+
74+
private class SourceListItemType
75+
{
76+
public int Id { get; set; }
77+
}
78+
79+
private class TargetChildType
80+
{
81+
public virtual int Id { get; set; }
82+
public virtual ICollection<TargetChildListItemType> ItemList { get; set; } = [];
83+
}
84+
85+
private class TargetChildListItemType
86+
{
87+
public virtual int Id { get; set; }
88+
}
89+
90+
private class TargetType
91+
{
92+
public virtual int Id { get; set; }
93+
94+
public virtual TargetChildType Child { get; set; }
95+
96+
public virtual ICollection<TargetListItemType> ItemList { get; set; } = [];
97+
}
98+
99+
private class TargetListItemType
100+
{
101+
public virtual int Id { get; set; }
102+
}
103+
}
104+
}

tests/AutoMapper.Extensions.ExpressionMapping.UnitTests/XpressionMapperTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,36 @@ public XpressionMapperTests()
1919

2020
#region Tests
2121

22+
[Fact]
23+
public void Map_expression_list()
24+
{
25+
//Arrange
26+
ICollection<Expression<Func<UserModel, object>>> selections = [s => s.AccountModel.Bal, s => s.AccountName];
27+
28+
//Act
29+
List<Expression<Func<User, object>>> selectionsMapped = [.. mapper.MapExpressionList<Expression<Func<User, object>>>(selections)];
30+
List<object> accounts = [.. Users.Select(selectionsMapped[0])];
31+
List<object> branches = [.. Users.Select(selectionsMapped[1])];
32+
33+
//Assert
34+
Assert.True(accounts.Count == 2 && branches.Count == 2);
35+
}
36+
37+
[Fact]
38+
public void Map_expression_list_using_two_generic_arguments_override()
39+
{
40+
//Arrange
41+
ICollection<Expression<Func<UserModel, object>>> selections = [s => s.AccountModel.Bal, s => s.AccountName];
42+
43+
//Act
44+
List<Expression<Func<User, object>>> selectionsMapped = [.. mapper.MapExpressionList<Expression < Func<UserModel, object>>, Expression <Func<User, object>>>(selections)];
45+
List<object> accounts = [.. Users.Select(selectionsMapped[0])];
46+
List<object> branches = [.. Users.Select(selectionsMapped[1])];
47+
48+
//Assert
49+
Assert.True(accounts.Count == 2 && branches.Count == 2);
50+
}
51+
2252
[Fact]
2353
public void Map_object_type_change()
2454
{
@@ -895,6 +925,16 @@ public void Can_map_expression_with_condittional_logic_while_deflattening()
895925
Assert.NotNull(mappedExpression);
896926
}
897927

928+
[Fact]
929+
public void Returns_null_when_soure_is_null()
930+
{
931+
Expression<Func<TestDTO, bool>> expr = null;
932+
933+
var mappedExpression = mapper.MapExpression<Expression<Func<TestEntity, bool>>>(expr);
934+
935+
Assert.Null(mappedExpression);
936+
}
937+
898938
[Fact]
899939
public void Can_map_expression_with_multiple_destination_parameters_of_the_same_type()
900940
{

0 commit comments

Comments
 (0)