Skip to content

Commit 9ccbd7c

Browse files
authored
Merge pull request #3870 from AutoMapper/mapfrom_identity
Handle identity lambda resolvers with ProjectTo subquery
2 parents 148c525 + f32b7ed commit 9ccbd7c

File tree

3 files changed

+122
-3
lines changed

3 files changed

+122
-3
lines changed

src/AutoMapper/Execution/ExpressionBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public static bool IsMemberPath(this LambdaExpression lambda, out Stack<Member>
232232
foreach (var member in members)
233233
{
234234
currentExpression = member.Expression;
235-
if (!(currentExpression is MemberExpression))
235+
if (currentExpression is not MemberExpression)
236236
{
237237
return false;
238238
}

src/AutoMapper/QueryableExtensions/ProjectionBuilder.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,27 @@ public SubQueryPath(MemberProjection[] members, LambdaExpression letExpression)
265265
Marker = Default(letExpression.Body.Type);
266266
LetExpression = letExpression;
267267
}
268-
public Expression GetSourceExpression(Expression parameter) => _members.Take(_members.Length - 1).Select(p => p.Expression).Aggregate(parameter,
269-
(left, right) => right is LambdaExpression lambda ? lambda.ReplaceParameters(left) : right.Replace(right.GetChain().Peek().Target, left));
268+
public Expression GetSourceExpression(Expression parameter)
269+
{
270+
Expression sourceExpression = parameter;
271+
for (int index = 0; index < _members.Length - 1; index++)
272+
{
273+
var sourceMember = _members[index].Expression;
274+
if (sourceMember is LambdaExpression lambda)
275+
{
276+
sourceExpression = lambda.ReplaceParameters(sourceExpression);
277+
}
278+
else
279+
{
280+
var chain = sourceMember.GetChain();
281+
if (chain.TryPeek(out var first))
282+
{
283+
sourceExpression = sourceMember.Replace(first.Target, sourceExpression);
284+
}
285+
}
286+
}
287+
return sourceExpression;
288+
}
270289
public PropertyDescription GetPropertyDescription() => new("__" + string.Join("#", _members.Select(p => p.MemberMap.DestinationName)), LetExpression.Body.Type);
271290
internal bool IsEquivalentTo(SubQueryPath other) => LetExpression == other.LetExpression && _members.Length == other._members.Length &&
272291
_members.Take(_members.Length - 1).Zip(other._members, (left, right) => left.MemberMap == right.MemberMap).All(item => item);

src/IntegrationTests/CustomMapFrom/MapObjectPropertyFromSubQuery.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,4 +959,104 @@ public void Should_project_ok()
959959
}
960960
}
961961
}
962+
public class MemberWithSubQueryIdentity : AutoMapperSpecBase
963+
{
964+
protected override MapperConfiguration CreateConfiguration() => new MapperConfiguration(cfg =>
965+
{
966+
cfg.CreateProjection<AEntity, Dto>()
967+
.ForMember(dst => dst.DtoSubWrapper, opt => opt.MapFrom(src => src));
968+
cfg.CreateProjection<AEntity, DtoSubWrapper>()
969+
.ForMember(dst => dst.DtoSub, opt => opt.MapFrom(src => src.BEntity.CEntities.FirstOrDefault(x => x.Id == src.CEntityId)));
970+
cfg.CreateProjection<CEntity, DtoSub>();
971+
});
972+
[Fact]
973+
public void Should_work()
974+
{
975+
var query = ProjectTo<Dto>(new ClientContext().AEntities);
976+
var result = query.Single();
977+
result.DtoSubWrapper.DtoSub.ShouldNotBeNull();
978+
result.DtoSubWrapper.DtoSub.SubString.ShouldBe("Test");
979+
}
980+
public class Dto
981+
{
982+
public int Id { get; set; }
983+
public DtoSubWrapper DtoSubWrapper { get; set; }
984+
}
985+
public class DtoSubWrapper
986+
{
987+
public DtoSub DtoSub { get; set; }
988+
}
989+
public class DtoSub
990+
{
991+
public int Id { get; set; }
992+
public string SubString { get; set; }
993+
}
994+
public class AEntity
995+
{
996+
public int Id { get; set; }
997+
public int BEntityId { get; set; }
998+
public int CEntityId { get; set; }
999+
public BEntity BEntity { get; set; }
1000+
}
1001+
public class BEntity
1002+
{
1003+
public int Id { get; set; }
1004+
public ICollection<CEntity> CEntities { get; set; }
1005+
}
1006+
public class CEntity
1007+
{
1008+
public int Id { get; set; }
1009+
public int BEntityId { get; set; }
1010+
public string SubString { get; set; }
1011+
public BEntity BEntity { get; set; }
1012+
}
1013+
class Initializer : DropCreateDatabaseAlways<ClientContext>
1014+
{
1015+
protected override void Seed(ClientContext context)
1016+
{
1017+
context.AEntities.Add(new AEntity
1018+
{
1019+
Id = 1,
1020+
BEntityId = 1,
1021+
CEntityId = 6,
1022+
BEntity = new BEntity
1023+
{
1024+
Id = 1,
1025+
CEntities = new List<CEntity>
1026+
{
1027+
new CEntity
1028+
{
1029+
Id = 6,
1030+
BEntityId = 1,
1031+
SubString = "Test"
1032+
}
1033+
}
1034+
},
1035+
});
1036+
}
1037+
}
1038+
class ClientContext : DbContext
1039+
{
1040+
protected override void OnModelCreating(DbModelBuilder modelBuilder)
1041+
{
1042+
Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
1043+
Database.SetInitializer(new Initializer());
1044+
1045+
modelBuilder.Entity<AEntity>()
1046+
.HasRequired(x => x.BEntity)
1047+
.WithMany()
1048+
.HasForeignKey(x => x.BEntityId);
1049+
1050+
modelBuilder.Entity<BEntity>()
1051+
.HasMany(x => x.CEntities)
1052+
.WithRequired(x => x.BEntity)
1053+
.HasForeignKey(x => x.BEntityId);
1054+
1055+
modelBuilder.Entity<CEntity>()
1056+
.Property(x => x.Id)
1057+
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
1058+
}
1059+
public DbSet<AEntity> AEntities { get; set; }
1060+
}
1061+
}
9621062
}

0 commit comments

Comments
 (0)