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
61 changes: 24 additions & 37 deletions src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,11 @@ protected override Expression VisitNew(NewExpression node)
}
}

private List<MemberBinding> CreateMemberBindings(ExpressionRequest request,
TypeMap typeMap,
Expression instanceParameter, TypePairCount typePairCount, LetPropertyMaps letPropertyMaps)
private List<MemberBinding> CreateMemberBindings(ExpressionRequest request, TypeMap typeMap, Expression instanceParameter, TypePairCount typePairCount, LetPropertyMaps letPropertyMaps)
{
var bindings = new List<MemberBinding>();

foreach (var propertyMap in typeMap.GetPropertyMaps().Where(pm => pm.CanResolveValue() && ReflectionHelper.CanBeSet(pm.DestinationProperty)))
foreach (var propertyMap in typeMap.GetPropertyMaps().Where(pm =>
(!pm.ExplicitExpansion || request.MembersToExpand.Contains(pm.DestinationProperty)) && pm.CanResolveValue() && ReflectionHelper.CanBeSet(pm.DestinationProperty)))
{
letPropertyMaps.Push(propertyMap);

Expand All @@ -222,44 +220,33 @@ private List<MemberBinding> CreateMemberBindings(ExpressionRequest request,
void CreateMemberBinding(PropertyMap propertyMap)
{
var result = ResolveExpression(propertyMap, request.SourceType, instanceParameter, letPropertyMaps);

if(propertyMap.ExplicitExpansion && !request.MembersToExpand.Contains(propertyMap.DestinationProperty))
var propertyTypeMap = _configurationProvider.ResolveTypeMap(result.Type, propertyMap.DestinationPropertyType);
var propertyRequest = new ExpressionRequest(result.Type, propertyMap.DestinationPropertyType, request.MembersToExpand, request);
if(propertyRequest.AlreadyExists)
{
return;
}
var propertyTypeMap = _configurationProvider.ResolveTypeMap(result.Type,
propertyMap.DestinationPropertyType);
var propertyRequest = new ExpressionRequest(result.Type, propertyMap.DestinationPropertyType, request.MembersToExpand, request);

if(!propertyRequest.AlreadyExists)
var binder = Binders.FirstOrDefault(b => b.IsMatch(propertyMap, propertyTypeMap, result));
if(binder == null)
{
var binder = Binders.FirstOrDefault(b => b.IsMatch(propertyMap, propertyTypeMap, result));

if(binder == null)
{
var message =
$"Unable to create a map expression from {propertyMap.SourceMember?.DeclaringType?.Name}.{propertyMap.SourceMember?.Name} ({result.Type}) to {propertyMap.DestinationProperty.DeclaringType?.Name}.{propertyMap.DestinationProperty.Name} ({propertyMap.DestinationPropertyType})";

throw new AutoMapperMappingException(message, null, typeMap.Types, typeMap, propertyMap);
}

var bindExpression = binder.Build(_configurationProvider, propertyMap, propertyTypeMap,
propertyRequest, result, typePairCount, letPropertyMaps);


if (bindExpression != null)
{
var rhs = propertyMap.ValueTransformers
.Concat(typeMap.ValueTransformers)
.Concat(typeMap.Profile.ValueTransformers)
.Where(vt => vt.IsMatch(propertyMap))
.Aggregate(bindExpression.Expression, (current, vtConfig) => ToType(ReplaceParameters(vtConfig.TransformerExpression, ToType(current, vtConfig.ValueType)), propertyMap.DestinationPropertyType));
var message =
$"Unable to create a map expression from {propertyMap.SourceMember?.DeclaringType?.Name}.{propertyMap.SourceMember?.Name} ({result.Type}) to {propertyMap.DestinationProperty.DeclaringType?.Name}.{propertyMap.DestinationProperty.Name} ({propertyMap.DestinationPropertyType})";
throw new AutoMapperMappingException(message, null, typeMap.Types, typeMap, propertyMap);
}
var bindExpression = binder.Build(_configurationProvider, propertyMap, propertyTypeMap, propertyRequest, result, typePairCount, letPropertyMaps);
if(bindExpression == null)
{
return;
}
var rhs = propertyMap.ValueTransformers
.Concat(typeMap.ValueTransformers)
.Concat(typeMap.Profile.ValueTransformers)
.Where(vt => vt.IsMatch(propertyMap))
.Aggregate(bindExpression.Expression, (current, vtConfig) => ToType(ReplaceParameters(vtConfig.TransformerExpression, ToType(current, vtConfig.ValueType)), propertyMap.DestinationPropertyType));

bindExpression = bindExpression.Update(rhs);
bindExpression = bindExpression.Update(rhs);

bindings.Add(bindExpression);
}
}
bindings.Add(bindExpression);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,100 @@ namespace AutoMapper.IntegrationTests
using System.Linq.Expressions;
using QueryableExtensions;

public class MapObjectPropertyFromSubQueryExplicitExpansion : AutoMapperSpecBase
{
protected override MapperConfiguration Configuration => new MapperConfiguration(cfg =>
{
cfg.CreateMap<Product, ProductModel>()
.ForMember(d => d.Price, o =>
{
o.MapFrom(source => source.Articles.Where(x => x.IsDefault && x.NationId == 1 && source.ECommercePublished).FirstOrDefault());
o.ExplicitExpansion();
});
cfg.CreateMap<Article, PriceModel>()
.ForMember(d => d.RegionId, o => o.MapFrom(s => s.NationId));
});

[Fact]
public void Should_map_ok()
{
using(var context = new ClientContext())
{
var projection = context.Products.ProjectTo<ProductModel>(Configuration);
var counter = new FirstOrDefaultCounter();
counter.Visit(projection.Expression);
counter.Count.ShouldBe(0);
var productModel = projection.First();
productModel.Price.ShouldBeNull();
productModel.Id.ShouldBe(1);
}
}

class FirstOrDefaultCounter : ExpressionVisitor
{
public int Count;

protected override Expression VisitMethodCall(MethodCallExpression node)
{
if(node.Method.Name == "FirstOrDefault")
{
Count++;
}
return base.VisitMethodCall(node);
}
}

public partial class Article
{
public int Id { get; set; }
public int ProductId { get; set; }
public bool IsDefault { get; set; }
public short NationId { get; set; }
public virtual Product Product { get; set; }
}

public partial class Product
{
public int Id { get; set; }
public string Name { get; set; }
public bool ECommercePublished { get; set; }
public virtual ICollection<Article> Articles { get; set; }
public int Value { get; }
public virtual List<Article> OtherArticles { get; }
}

public class PriceModel
{
public int Id { get; set; }
public short RegionId { get; set; }
public bool IsDefault { get; set; }
}

public class ProductModel
{
public int Id { get; set; }
public PriceModel Price { get; set; }
}

class Initializer : DropCreateDatabaseAlways<ClientContext>
{
protected override void Seed(ClientContext context)
{
context.Products.Add(new Product { ECommercePublished = true, Articles = new[] { new Article { IsDefault = true, NationId = 1, ProductId = 1 } } });
}
}

class ClientContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new Initializer());
}

public DbSet<Product> Products { get; set; }
}
}

public class MapObjectPropertyFromSubQuery : AutoMapperSpecBase
{
protected override MapperConfiguration Configuration => new MapperConfiguration(cfg=>
Expand Down