Skip to content

Commit

Permalink
Implemented IgnoreIf() - conditional ignoring of properties
Browse files Browse the repository at this point in the history
  • Loading branch information
satano committed Aug 5, 2016
1 parent 67139c3 commit 98ee605
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 11 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,14 @@ Mapster will automatically map properties with the same names. You can ignore me
.NewConfig()
.Ignore(dest => dest.Id);

You can ignore members conditionally, with condition based on source or target. When the condition is met, mapping of the property
will be skipped altogether. This is the difference from custom `Map` with condition, where destination is set to `null`
when condition is met.

TypeAdapterConfig<TSource, TDestination>
.NewConfig()
.IgnoreIf((src, dest) => !string.IsNullOrEmpty(dest.Name), dest => dest.Name);

You can ignore members annotated with specific attributes by using the `IgnoreAttribute` method.

TypeAdapterConfig<TSource, TDestination>
Expand Down
1 change: 1 addition & 0 deletions src/Mapster.Tests/Mapster.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<Compile Include="WhenCloningConfig.cs" />
<Compile Include="WhenCompilingConfig.cs" />
<Compile Include="WhenCreatingConfigInstance.cs" />
<Compile Include="WhenIgnoringConditionally.cs" />
<Compile Include="WhenMappingDerived.cs" />
<Compile Include="WhenMappingWithDictionary.cs" />
<Compile Include="WhenMappingRecordTypes.cs" />
Expand Down
148 changes: 148 additions & 0 deletions src/Mapster.Tests/WhenIgnoringConditionally.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using NUnit.Framework;
using Shouldly;

namespace Mapster.Tests
{
[TestFixture]
public class WhenIgnoringConditionally
{

#region Tests

[Test]
public void True_Constant_Ignores_Map()
{
TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig()
.IgnoreIf((src, dest) => true, dest => dest.Name)
.Compile();

var poco = new SimplePoco { Id = 1, Name = "TestName" };
SimpleDto dto = TypeAdapter.Adapt<SimplePoco, SimpleDto>(poco);

dto.Id.ShouldBe(1);
dto.Name.ShouldBeNull();
}

[Test]
public void True_Constant_Ignores_Map_To_Target()
{
TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig()
.IgnoreIf((src, dest) => true, dest => dest.Name)
.Compile();

var poco = new SimplePoco { Id = 1, Name = "TestName" };
var dto = new SimpleDto { Id = 999, Name = "DtoName" };
TypeAdapter.Adapt(poco, dto);

dto.Id.ShouldBe(1);
dto.Name.ShouldBe("DtoName");
}

[Test]
public void True_Condition_Ignores_Map()
{
TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig()
.IgnoreIf((src, dest) => src.Name == "TestName", dest => dest.Name)
.Compile();

var poco = new SimplePoco { Id = 1, Name = "TestName" };
SimpleDto dto = TypeAdapter.Adapt<SimplePoco, SimpleDto>(poco);

dto.Id.ShouldBe(1);
dto.Name.ShouldBeNull();
}

[Test]
public void True_Condition_Ignores_Map_To_Target()
{
TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig()
.IgnoreIf((src, dest) => src.Name == "TestName", dest => dest.Name)
.Compile();

var poco = new SimplePoco { Id = 1, Name = "TestName" };
var dto = new SimpleDto { Id = 999, Name = "DtoName" };
TypeAdapter.Adapt(poco, dto);

dto.Id.ShouldBe(1);
dto.Name.ShouldBe("DtoName");
}

[Test]
public void Null_Condition_Ignores_Map()
{
TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig()
.IgnoreIf(null, dest => dest.Name)
.Compile();

var poco = new SimplePoco { Id = 1, Name = "TestName" };
var dto = TypeAdapter.Adapt<SimplePoco, SimpleDto>(poco);

dto.Id.ShouldBe(1);
dto.Name.ShouldBeNull();
}

[Test]
public void Null_Condition_Ignores_Map_To_Target()
{
TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig()
.IgnoreIf(null, dest => dest.Name)
.Compile();

var poco = new SimplePoco { Id = 1, Name = "TestName" };
var dto = new SimpleDto { Id = 999, Name = "DtoName" };
TypeAdapter.Adapt(poco, dto);

dto.Id.ShouldBe(1);
dto.Name.ShouldBe("DtoName");
}

[Test]
public void True_Condition_On_Target_Ignores_Map()
{
TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig()
.IgnoreIf((src, dest) => !string.IsNullOrEmpty(dest.Name), dest => dest.Name)
.Compile();

var poco = new SimplePoco { Id = 1, Name = "TestName" };
var dto = TypeAdapter.Adapt<SimplePoco, SimpleDto>(poco);

dto.Id.ShouldBe(1);
dto.Name.ShouldBe("TestName");
}

[Test]
public void True_Condition_On_Target_Ignores_Map_To_Target()
{
TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig()
.IgnoreIf((src, dest) => !string.IsNullOrEmpty(dest.Name), dest => dest.Name)
.Compile();

var poco = new SimplePoco { Id = 1, Name = "TestName" };
var dto = new SimpleDto { Id = 999, Name = "DtoName" };
TypeAdapter.Adapt(poco, dto);

dto.Id.ShouldBe(1);
dto.Name.ShouldBe("DtoName");
}

#endregion


#region TestClasses

public class SimplePoco
{
public int Id { get; set; }
public string Name { get; set; }
}

public class SimpleDto
{
public int Id { get; set; }
public string Name { get; set; }
}

#endregion

}
}
3 changes: 3 additions & 0 deletions src/Mapster/Adapters/BaseAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reflection;
using Mapster.Models;
using Mapster.Utils;
using System.Linq;

namespace Mapster.Adapters
{
Expand Down Expand Up @@ -55,6 +56,8 @@ protected virtual bool CanInline(Expression source, Expression destination, Comp
{
if (arg.MapType == MapType.MapToTarget)
return false;
if (arg.Settings.IgnoreMembers.Any(item => item.Value != null))
return false;
var constructUsing = arg.Settings.ConstructUsingFactory?.Invoke(arg);
if (constructUsing != null &&
constructUsing.Body.NodeType != ExpressionType.New &&
Expand Down
17 changes: 13 additions & 4 deletions src/Mapster/Adapters/BaseClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Linq.Expressions;
using Mapster.Models;
using Mapster.Utils;

namespace Mapster.Adapters
{
Expand All @@ -23,7 +24,8 @@ protected ClassMapping CreateClassConverter(Expression source, Expression destin

foreach (var destinationMember in destinationMembers)
{
if (ProcessIgnores(arg.Settings, destinationMember)) continue;
LambdaExpression setterCondition;
if (ProcessIgnores(arg.Settings, destinationMember, source, out setterCondition)) continue;

var member = destinationMember;
var getter = arg.Settings.ValueAccessingStrategies
Expand All @@ -37,6 +39,7 @@ protected ClassMapping CreateClassConverter(Expression source, Expression destin
Getter = getter,
Setter = destinationMember.GetExpression(destination),
SetterInfo = destinationMember.Info,
SetterCondition = setterCondition,
};
properties.Add(propertyModel);
}
Expand Down Expand Up @@ -68,10 +71,16 @@ protected ClassMapping CreateClassConverter(Expression source, Expression destin
};
}

private static bool ProcessIgnores(TypeAdapterSettings config, IMemberModel destinationMember)
private static bool ProcessIgnores(
TypeAdapterSettings config,
IMemberModel destinationMember,
Expression source,
out LambdaExpression condition)
{
if (config.IgnoreMembers.Contains(destinationMember.Name))
return true;
if (config.IgnoreMembers.TryGetValue(destinationMember.Name, out condition)) {
return condition == null;
}

var attributes = destinationMember.GetCustomAttributes(true).Select(attr => attr.GetType());
return config.IgnoreAttributes.Overlaps(attributes);
}
Expand Down
8 changes: 7 additions & 1 deletion src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
using System.Linq.Expressions;
using System.Reflection;
using Mapster.Models;
using Mapster.Utils;

namespace Mapster.Adapters
{
/// <summary>
/// Maps one class to another.
/// </summary>
/// <remarks>The operations in this class must be extremely fast. Make sure to benchmark before making **any** changes in here.
/// <remarks>The operations in this class must be extremely fast. Make sure to benchmark before making **any** changes in here.
/// The core Adapt method is critically important to performance.
/// </remarks>
internal class ClassAdapter : BaseClassAdapter
Expand Down Expand Up @@ -64,6 +65,11 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
var condition = Expression.NotEqual(property.Getter, Expression.Constant(null, property.Getter.Type));
itemAssign = Expression.IfThen(condition, itemAssign);
}

if (property.SetterCondition != null)
{
itemAssign = Expression.IfThen(Expression.Not(property.SetterCondition.Apply(source, destination)), itemAssign);
}
lines.Add(itemAssign);
}

Expand Down
1 change: 1 addition & 0 deletions src/Mapster/Models/MemberMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ internal class MemberMapping
public Expression Setter;

public object SetterInfo;
public LambdaExpression SetterCondition;
}
}
29 changes: 25 additions & 4 deletions src/Mapster/TypeAdapterSetter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ public static class TypeAdapterSetterExtensions
{
setter.CheckCompiled();

setter.Settings.IgnoreMembers.UnionWith(names);
foreach (var name in names)
{
setter.Settings.IgnoreMembers[name] = null;
}
return setter;
}

Expand Down Expand Up @@ -99,11 +102,13 @@ public TypeAdapterSetter<TDestination> Ignore(params Expression<Func<TDestinatio
{
this.CheckCompiled();

Settings.IgnoreMembers.UnionWith(members.Select(member => ReflectionUtils.GetMemberInfo(member).Member.Name));
foreach (var member in members)
{
Settings.IgnoreMembers[ReflectionUtils.GetMemberInfo(member).Member.Name] = null;
}
return this;
}


public TypeAdapterSetter<TDestination> Map<TDestinationMember, TSourceMember>(
Expression<Func<TDestination, TDestinationMember>> member,
Expression<Func<TSourceMember>> source)
Expand Down Expand Up @@ -158,7 +163,23 @@ internal TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig paren
{
this.CheckCompiled();

Settings.IgnoreMembers.UnionWith(members.Select(member => ReflectionUtils.GetMemberInfo(member).Member.Name));
foreach (var member in members)
{
Settings.IgnoreMembers[ReflectionUtils.GetMemberInfo(member).Member.Name] = null;
}
return this;
}

public TypeAdapterSetter<TSource, TDestination> IgnoreIf(
Expression<Func<TSource, TDestination, bool>> condition,
params Expression<Func<TDestination, object>>[] members)
{
this.CheckCompiled();

foreach (var member in members)
{
Settings.IgnoreMembers[ReflectionUtils.GetMemberInfo(member).Member.Name] = condition;
}
return this;
}

Expand Down
7 changes: 5 additions & 2 deletions src/Mapster/TypeAdapterSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public enum MapType

public class TypeAdapterSettings
{
public HashSet<string> IgnoreMembers { get; internal set; } = new HashSet<string>();
public Dictionary<string, LambdaExpression> IgnoreMembers { get; internal set; } = new Dictionary<string, LambdaExpression>();
public HashSet<Type> IgnoreAttributes { get; internal set; } = new HashSet<Type>();
public TransformsCollection DestinationTransforms { get; internal set; } = new TransformsCollection();
public NameMatchingStrategy NameMatchingStrategy { get; internal set; } = new NameMatchingStrategy();
Expand Down Expand Up @@ -53,7 +53,10 @@ public void Apply(TypeAdapterSettings other)
if (this.IgnoreNullValues == null)
this.IgnoreNullValues = other.IgnoreNullValues;

this.IgnoreMembers.UnionWith(other.IgnoreMembers);
foreach (var member in other.IgnoreMembers)
{
this.IgnoreMembers[member.Key] = member.Value;
}
this.IgnoreAttributes.UnionWith(other.IgnoreAttributes);
this.NameMatchingStrategy.Apply(other.NameMatchingStrategy);
this.DestinationTransforms.TryAdd(other.DestinationTransforms.Transforms);
Expand Down

0 comments on commit 98ee605

Please sign in to comment.