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
38 changes: 38 additions & 0 deletions docs/8.0-Upgrade-Guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 8.0 Upgrade Guide

## ProjectUsing

The `ProjectUsing` method consolidated with `ConvertUsing`:

```c#
// IMappingExpression

// Old
void ConvertUsing(Func<TSource, TDestination> mappingFunction);
void ProjectUsing(Expression<Func<TSource, TDestination>> mappingExpression);

// New
void ConvertUsing(Expression<Func<TSource, TDestination>> mappingExpression);
```

To migrate, replace all usages of `ProjectUsing` with `ConvertUsing`.

The `ConvertUsing` expression-based method will be used for both in-memory mapping and LINQ projections. You cannot have separate configuration for in-memory vs. LINQ projections.

### Existing `ConvertUsing` usages

The change from `Func` to `Expression` may break some existing usages. Namely:

- `ConvertUsing` using lambda statements, method groups, or delegates
- Dual configuration of `ProjectUsing` and `ConvertUsing`

For the first case, you may either:

- Convert to a lambda expression
- Move to the `Func`-based overloads

The `Func`-based overloads accept more parameters, so you may have to add the parameters to your delegates.

### Motivation

Simplify overloads, and to make it clear that you cannot have separate configuration for LINQ projections vs. in-memory mapping.
4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ domain and application layer.

AutoMapper supports the following platforms:

* .NET 4.5+
* `.NET Standard 1.3+ <https://docs.microsoft.com/en-us/dotnet/standard/net-standard>`
* .NET 4.6.1+
* `.NET Standard 2.0+ https://docs.microsoft.com/en-us/dotnet/standard/net-standard`

New to AutoMapper? Check out the :doc:`Getting-started` page first.
Expand All @@ -28,6 +27,7 @@ New to AutoMapper? Check out the :doc:`Getting-started` page first.

Getting-started
5.0-Upgrade-Guide
8.0-Upgrade-Guide
Static-and-Instance-API
Migrating-from-static-API
Conventions
Expand Down
19 changes: 4 additions & 15 deletions src/AutoMapper/Configuration/MappingExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,15 @@ public IMappingExpression ForMember(string name, Action<IMemberConfigurationExpr
public new IMappingExpression ConstructUsing(Func<object, object> ctor)
=> (IMappingExpression)base.ConstructUsing(ctor);

public new IMappingExpression ConstructUsing(Func<object, ResolutionContext, object> ctor)
public new IMappingExpression ConstructUsing(Func<object, ResolutionContext, object> ctor)
=> (IMappingExpression)base.ConstructUsing(ctor);

public IMappingExpression ConstructProjectionUsing(LambdaExpression ctor)
{
TypeMapActions.Add(tm => tm.ConstructExpression = ctor);

return this;
}
}

public new IMappingExpression ValidateMemberList(MemberList memberList)
=> (IMappingExpression) base.ValidateMemberList(memberList);
Expand Down Expand Up @@ -274,11 +274,6 @@ public IMappingExpression<TSource, TDestination> IncludeBase(Type sourceBase, Ty
return this;
}

public void ProjectUsing(Expression<Func<TSource, TDestination>> projectionExpression)
{
TypeMapActions.Add(tm => tm.CustomProjection = projectionExpression);
}

public IMappingExpression<TSource, TDestination> MaxDepth(int depth)
{
TypeMapActions.Add(tm => tm.MaxDepth = depth);
Expand Down Expand Up @@ -326,15 +321,9 @@ public IMappingExpression<TSource, TDestination> ForSourceMember(string sourceMe
return this;
}

public void ConvertUsing(Func<TSource, TDestination> mappingFunction)
public void ConvertUsing(Expression<Func<TSource, TDestination>> mappingFunction)
{
TypeMapActions.Add(tm =>
{
Expression<Func<TSource, TDestination, ResolutionContext, TDestination>> expr =
(src, dest, ctxt) => mappingFunction(src);

tm.CustomMapper = expr;
});
TypeMapActions.Add(tm => tm.CustomProjection = mappingFunction);
}

public void ConvertUsing(Func<TSource, TDestination, TDestination> mappingFunction)
Expand Down
42 changes: 23 additions & 19 deletions src/AutoMapper/IMappingExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public interface IMappingExpression
/// <param name="depth">Number of levels to limit to</param>
/// <returns>Itself</returns>
IMappingExpression MaxDepth(int depth);

/// <summary>
/// Supply a custom instantiation expression for the destination type for LINQ projection
/// </summary>
Expand All @@ -64,12 +64,6 @@ public interface IMappingExpression
/// <returns>Itself</returns>
IMappingExpression ConstructUsing(Func<object, object> ctor);

/// <summary>
/// Skip member mapping and use a custom expression during LINQ projection
/// </summary>
/// <param name="projectionExpression">Projection expression</param>
void ProjectUsing(Expression<Func<object, object>> projectionExpression);

/// <summary>
/// Customize configuration for all members
/// </summary>
Expand All @@ -89,7 +83,13 @@ public interface IMappingExpression
/// <param name="memberOptions">Callback for member configuration options</param>
/// <returns>Itself</returns>
IMappingExpression ForSourceMember(string sourceMemberName, Action<ISourceMemberConfigurationExpression> memberOptions);


/// <summary>
/// Skip member mapping and use a custom expression to convert to the destination type
/// </summary>
/// <param name="mappingExpression">Callback to convert from source type to destination type</param>
void ConvertUsing(Expression<Func<object, object>> mappingExpression);

/// <summary>
/// Skip normal member mapping and convert using a <see cref="ITypeConverter{TSource,TDestination}"/> instantiated during mapping
/// </summary>
Expand Down Expand Up @@ -204,7 +204,7 @@ IMappingExpression<TSource, TDestination> ForPath<TMember>(Expression<Func<TDest
/// <summary>
/// Preserve object identity. Useful for circular references.
/// </summary>
/// <returns></returns>
/// <returns>Itself</returns>
IMappingExpression<TSource, TDestination> PreserveReferences();

/// <summary>
Expand All @@ -227,7 +227,7 @@ IMappingExpression<TSource, TDestination> ForMember<TMember>(Expression<Func<TDe
/// </summary>
/// <param name="name">Destination member name</param>
/// <param name="memberOptions">Callback for member options</param>
/// <returns></returns>
/// <returns>Itself</returns>
IMappingExpression<TSource, TDestination> ForMember(string name,
Action<IMemberConfigurationExpression<TSource, TDestination, object>> memberOptions);

Expand Down Expand Up @@ -276,58 +276,59 @@ IMappingExpression<TSource, TDestination> Include<TOtherSource, TOtherDestinatio
IMappingExpression<TSource, TDestination> Include(Type derivedSourceType, Type derivedDestinationType);

/// <summary>
/// Skip member mapping and use a custom expression during LINQ projection
/// </summary>
/// <param name="projectionExpression">Projection expression</param>
void ProjectUsing(Expression<Func<TSource, TDestination>> projectionExpression);

/// <summary>
/// Skip member mapping and use a custom function to convert to the destination type
/// Skip member mapping and use a custom expression to convert to the destination type
/// </summary>
/// <param name="mappingFunction">Callback to convert from source type to destination type</param>
void ConvertUsing(Func<TSource, TDestination> mappingFunction);
/// <param name="mappingExpression">Callback to convert from source type to destination type</param>
void ConvertUsing(Expression<Func<TSource, TDestination>> mappingExpression);

/// <summary>
/// Skip member mapping and use a custom function to convert to the destination type
/// </summary>
/// <remarks>Not used for LINQ projection (ProjectTo)</remarks>
/// <param name="mappingFunction">Callback to convert from source type to destination type, including destination object</param>
void ConvertUsing(Func<TSource, TDestination, TDestination> mappingFunction);

/// <summary>
/// Skip member mapping and use a custom function to convert to the destination type
/// </summary>
/// <remarks>Not used for LINQ projection (ProjectTo)</remarks>
/// <param name="mappingFunction">Callback to convert from source type to destination type, with source, destination and context</param>
void ConvertUsing(Func<TSource, TDestination, ResolutionContext, TDestination> mappingFunction);

/// <summary>
/// Skip member mapping and use a custom type converter instance to convert to the destination type
/// </summary>
/// <remarks>Not used for LINQ projection (ProjectTo)</remarks>
/// <param name="converter">Type converter instance</param>
void ConvertUsing(ITypeConverter<TSource, TDestination> converter);

/// <summary>
/// Skip member mapping and use a custom type converter instance to convert to the destination type
/// </summary>
/// <remarks>Not used for LINQ projection (ProjectTo)</remarks>
/// <typeparam name="TTypeConverter">Type converter type</typeparam>
void ConvertUsing<TTypeConverter>() where TTypeConverter : ITypeConverter<TSource, TDestination>;

/// <summary>
/// Execute a custom function to the source and/or destination types before member mapping
/// </summary>
/// <remarks>Not used for LINQ projection (ProjectTo)</remarks>
/// <param name="beforeFunction">Callback for the source/destination types</param>
/// <returns>Itself</returns>
IMappingExpression<TSource, TDestination> BeforeMap(Action<TSource, TDestination> beforeFunction);

/// <summary>
/// Execute a custom function to the source and/or destination types before member mapping
/// </summary>
/// <remarks>Not used for LINQ projection (ProjectTo)</remarks>
/// <param name="beforeFunction">Callback for the source/destination types</param>
/// <returns>Itself</returns>
IMappingExpression<TSource, TDestination> BeforeMap(Action<TSource, TDestination, ResolutionContext> beforeFunction);

/// <summary>
/// Execute a custom mapping action before member mapping
/// </summary>
/// <remarks>Not used for LINQ projection (ProjectTo)</remarks>
/// <typeparam name="TMappingAction">Mapping action type instantiated during mapping</typeparam>
/// <returns>Itself</returns>
IMappingExpression<TSource, TDestination> BeforeMap<TMappingAction>()
Expand All @@ -336,20 +337,23 @@ IMappingExpression<TSource, TDestination> BeforeMap<TMappingAction>()
/// <summary>
/// Execute a custom function to the source and/or destination types after member mapping
/// </summary>
/// <remarks>Not used for LINQ projection (ProjectTo)</remarks>
/// <param name="afterFunction">Callback for the source/destination types</param>
/// <returns>Itself</returns>
IMappingExpression<TSource, TDestination> AfterMap(Action<TSource, TDestination> afterFunction);

/// <summary>
/// Execute a custom function to the source and/or destination types after member mapping
/// </summary>
/// <remarks>Not used for LINQ projection (ProjectTo)</remarks>
/// <param name="afterFunction">Callback for the source/destination types</param>
/// <returns>Itself</returns>
IMappingExpression<TSource, TDestination> AfterMap(Action<TSource, TDestination, ResolutionContext> afterFunction);

/// <summary>
/// Execute a custom mapping action after member mapping
/// </summary>
/// <remarks>Not used for LINQ projection (ProjectTo)</remarks>
/// <typeparam name="TMappingAction">Mapping action type instantiated during mapping</typeparam>
/// <returns>Itself</returns>
IMappingExpression<TSource, TDestination> AfterMap<TMappingAction>()
Expand Down
6 changes: 3 additions & 3 deletions src/IntegrationTests/BuiltInTypes/ProjectUsing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public class MyProfile : Profile
public MyProfile()
{
CreateMap<MyTable, MyTableModel>();
CreateMap<int, MyEnum>().ProjectUsing(x => (MyEnum)x);
CreateMap<int?, MyEnum>().ProjectUsing(x => x.HasValue ? (MyEnum)x.Value : MyEnum.Value1);
CreateMap<int, MyEnum>().ConvertUsing(x => (MyEnum)x);
CreateMap<int?, MyEnum>().ConvertUsing(x => x.HasValue ? (MyEnum)x.Value : MyEnum.Value1);
}
}

Expand Down Expand Up @@ -126,7 +126,7 @@ protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
cfg.CreateMap<Parent, ParentVM>();
cfg.CreateMap<Children, int>()
.ProjectUsing(c => c.ID);
.ConvertUsing(c => c.ID);
});

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion src/IntegrationTests/Inheritance/DerivedComplexTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected override void Seed(Context context)
protected override MapperConfiguration Configuration => new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerViewModel>();
cfg.CreateMap<LocalizedString, string>().ProjectUsing(v=>v.Value);
cfg.CreateMap<LocalizedString, string>().ConvertUsing(v => v.Value);
});

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion src/UnitTests/ArraysAndLists.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public class When_mapping_a_primitive_array_with_custom_mapping_function : AutoM
int[] _source = Enumerable.Range(1, 10).ToArray();
int[] _destination;

protected override MapperConfiguration Configuration => new MapperConfiguration(c => c.CreateMap<int, int>().ProjectUsing(i => i * 1000));
protected override MapperConfiguration Configuration => new MapperConfiguration(c => c.CreateMap<int, int>().ConstructUsing(i => i * 1000));

protected override void Because_of()
{
Expand Down
2 changes: 1 addition & 1 deletion src/UnitTests/Bug/CannotProjectStringToNullableEnum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void Should_project_string_to_nullable_enum()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<string, DummyTypes?>().ProjectUsing(s => (DummyTypes)System.Enum.Parse(typeof(DummyTypes),s));
cfg.CreateMap<string, DummyTypes?>().ConvertUsing(s => (DummyTypes)System.Enum.Parse(typeof(DummyTypes),s));
cfg.CreateMap<DummySource, DummyDestination>();
});

Expand Down
2 changes: 1 addition & 1 deletion src/UnitTests/Bug/ProjectUsingTheQueriedEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Destination

protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Destination>().ProjectUsing(s => new Destination {Number = 23 + s.Number});
cfg.CreateMap<Source, Destination>().ConvertUsing(s => new Destination {Number = 23 + s.Number});
});

protected override void Because_of()
Expand Down
4 changes: 2 additions & 2 deletions src/UnitTests/CustomMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ public When_specifying_a_custom_translator_using_projection()
public void Should_use_the_custom_translator()
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Source, Destination>()
.ProjectUsing(s => new Destination { Value = s.Value + 10 }));
.ConvertUsing(s => new Destination { Value = s.Value + 10 }));

_dest = config.CreateMapper().Map<Source, Destination>(_source);
_dest.Value.ShouldBe(20);
Expand All @@ -981,7 +981,7 @@ public void Should_ignore_other_mapping_rules()
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.AnotherValue))
.ProjectUsing(s => new Destination { Value = s.Value + 10 }));
.ConvertUsing(s => new Destination { Value = s.Value + 10 }));

_dest = config.CreateMapper().Map<Source, Destination>(_source);
_dest.Value.ShouldBe(20);
Expand Down
6 changes: 3 additions & 3 deletions src/UnitTests/IMappingExpression/NonGenericProjectEnumTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public NonGenericProjectEnumTest()
_config = new MapperConfiguration(cfg =>
{
cfg.CreateMap(typeof(Customer), typeof(CustomerDto));
cfg.CreateMap(typeof(CustomerType), typeof(string)).ProjectUsing(ct => ct.ToString().ToUpper());
cfg.CreateMap(typeof(CustomerType), typeof(string)).ConvertUsing(ct => ct.ToString().ToUpper());
});
}

Expand Down Expand Up @@ -64,7 +64,7 @@ public NonGenericProjectAndMapEnumTest()
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap(typeof (Customer), typeof (CustomerDto));
cfg.CreateMap(typeof (CustomerType), typeof (string)).ProjectUsing(ct => ct.ToString().ToUpper());
cfg.CreateMap(typeof (CustomerType), typeof (string)).ConvertUsing(ct => ct.ToString().ToUpper());
});
_mapper = config.CreateMapper();
}
Expand Down Expand Up @@ -118,7 +118,7 @@ public class Dest

protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.CreateMap(typeof (Source), typeof (Dest)).ProjectUsing(src => new Dest {Value = 10});
cfg.CreateMap(typeof (Source), typeof (Dest)).ConvertUsing(src => new Dest {Value = 10});
});

[Fact]
Expand Down
4 changes: 2 additions & 2 deletions src/UnitTests/Projection/ProjectEnumTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public ProjectEnumTest()
_config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerDto>();
cfg.CreateMap<CustomerType, string>().ProjectUsing(ct => ct.ToString().ToUpper());
cfg.CreateMap<CustomerType, string>().ConvertUsing(ct => ct.ToString().ToUpper());
});
}

Expand Down Expand Up @@ -70,7 +70,7 @@ public class Dest
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Dest>()
.ProjectUsing(src => new Dest {Value = 10});
.ConvertUsing(src => new Dest {Value = 10});
});

[Fact]
Expand Down
6 changes: 3 additions & 3 deletions src/UnitTests/Projection/ProjectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public class ProjectionTests
public void Direct_assignability_shouldnt_trump_custom_projection() {
var config = new MapperConfiguration(x => {
x.CreateMap<string, string>()
.ProjectUsing(s => _niceGreeting);
.ConvertUsing(s => _niceGreeting);

x.CreateMap<Source, Target>();
x.CreateMap<SourceChild, TargetChild>();
Expand All @@ -94,7 +94,7 @@ public void Direct_assignability_shouldnt_trump_custom_projection() {
public void Root_is_subject_to_custom_projection() {
var config = new MapperConfiguration(x => {
x.CreateMap<Source, Target>()
.ProjectUsing(s => new Target() { Greeting = _niceGreeting });
.ConvertUsing(s => new Target() { Greeting = _niceGreeting });
});

var target = new[] { new Source() }
Expand All @@ -110,7 +110,7 @@ public void Root_is_subject_to_custom_projection() {
public void Child_nodes_are_subject_to_custom_projection() {
var config = new MapperConfiguration(x => {
x.CreateMap<SourceChild, TargetChild>()
.ProjectUsing(s => new TargetChild() { Greeting = _niceGreeting });
.ConvertUsing(s => new TargetChild() { Greeting = _niceGreeting });

x.CreateMap<Source, Target>();
});
Expand Down