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
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<!-- Properties related to build/pack -->
<IsPackable>false</IsPackable>
<Version>10.1.0-pre02</Version>
<Version>10.0.12</Version>
<MapsterPluginsTFMs>netstandard2.0;net10.0;net9.0;net8.0</MapsterPluginsTFMs>
<MapsterTFMs>netstandard2.0;net10.0;net9.0;net8.0</MapsterTFMs>
<MapsterEFCoreTFMs>net10.0;net9.0;net8.0</MapsterEFCoreTFMs>
Expand Down
43 changes: 43 additions & 0 deletions src/Mapster.Tests/WhenIgnoringConditionally.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,43 @@ public void IgnoreIf_Apply_To_RecordType()
dto.Name.ShouldBeNull();
}

// Regression test for https://github.com/MapsterMapper/Mapster/issues/1007
// Unlike SimpleRecord above (which maps every member through the primary
// constructor), NativeRecord is a real C# `record` whose Name property is
// NOT part of the primary constructor — it's a plain settable auto-property.
// Members mapped that way go through RecordTypeAdapter.RecordInlineExpression,
// a separate code path from constructor-argument mapping, which used to
// ignore member.Ignore.Condition entirely.
[TestMethod]
public void IgnoreIf_Apply_To_RecordType_Property_Not_In_Constructor_Map()
{
TypeAdapterConfig<SimplePoco, NativeRecord>.NewConfig()
.IgnoreIf((src, dest) => src.Name == "TestName", dest => dest.Name)
.Compile();

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

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

[TestMethod]
public void IgnoreIf_Apply_To_RecordType_Property_Not_In_Constructor_MapToTarget()
{
TypeAdapterConfig<SimplePoco, NativeRecord>.NewConfig()
.IgnoreIf((src, dest) => src.Name == "TestName", dest => dest.Name)
.Compile();

var poco = new SimplePoco { Id = 1, Name = "TestName" };
var dto = new NativeRecord { Id = 999, Name = "DtoName" };

var result = TypeAdapter.Adapt(poco, dto);

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

#endregion


Expand Down Expand Up @@ -202,6 +239,12 @@ public SimpleRecord(int id, string name)
}
}

public record NativeRecord
{
public int Id { get; set; }
public string Name { get; set; }
}

#endregion

}
Expand Down
22 changes: 22 additions & 0 deletions src/Mapster/Adapters/RecordTypeAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
}


return RecordInlineExpression(source, destination, arg, installExpr); // Activator field when not include in public ctor

Check warning on line 51 in src/Mapster/Adapters/RecordTypeAdapter.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.
}

private Expression? RecordInlineExpression(Expression source, Expression? destination, CompileArgument arg, Expression installExpr)
Expand Down Expand Up @@ -96,13 +96,35 @@
binEx.Right is ConstantExpression { Value: null })
adapt = condEx.IfFalse;
}
var destinationCompareNull = Expression.Equal(destination, Expression.Constant(null, destination.Type));

Check warning on line 99 in src/Mapster/Adapters/RecordTypeAdapter.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
var sourceCondition = Expression.NotEqual(member.Getter, Expression.Constant(null, member.Getter.Type));
var destinationCanbeNull = Expression.Condition(destinationCompareNull, member.DestinationMember.Type.CreateDefault(arg), member.DestinationMember.GetExpression(destination));
adapt = Expression.Condition(sourceCondition, adapt, destinationCanbeNull);
}
}

// IgnoreIf(...) support — unlike ClassAdapter (which drops the whole
// member statement into an `if (!condition) { ... }` block), record
// members are bound inline inside a MemberInit, so the condition is
// expressed as a ternary: keep the computed value when the condition
// is false, otherwise fall back to whatever the member should be left
// as (the existing destination value for MapToTarget, or the type's
// default value when constructing a brand-new instance).
if (arg.MapType != MapType.Projection && member.Ignore.Condition != null)
{
var conditionDestination = destination ?? arg.DestinationType.CreateDefault();
var conditionBody = member.Ignore.IsChildPath
? member.Ignore.Condition.Body
: member.Ignore.Condition.Apply(arg.MapType, source, conditionDestination);
var notIgnored = ExpressionEx.Not(conditionBody);

var fallback = arg.MapType == MapType.MapToTarget && destination != null
? member.DestinationMember.GetExpression(destination)
: member.DestinationMember.Type.CreateDefault();

adapt = Expression.Condition(notIgnored, adapt, fallback);
}

//special null property check for projection
//if we don't set null to property, EF will create empty object
//except collection type & complex type which cannot be null
Expand Down Expand Up @@ -145,7 +167,7 @@
contructorMembers.Any(x => string.Equals(x.Name, member.Name, StringComparison.InvariantCultureIgnoreCase)))
continue;

lines.Add(Expression.Bind((MemberInfo)member.Info, Expression.MakeMemberAccess(destination, (MemberInfo)member.Info)));

Check warning on line 170 in src/Mapster/Adapters/RecordTypeAdapter.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.

Check warning on line 170 in src/Mapster/Adapters/RecordTypeAdapter.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.
}

return lines;
Expand Down Expand Up @@ -192,9 +214,9 @@

if (arg.MapType == MapType.MapToTarget)
{
var var2Param = ClassConverterContext.Members.Where(x => x.DestinationMember.Name == member.DestinationMember.Name).FirstOrDefault();

Check warning on line 217 in src/Mapster/Adapters/RecordTypeAdapter.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Expression destMemberVar2 = var2Param.DestinationMember.GetExpression(var2Param.Destination);

Check warning on line 219 in src/Mapster/Adapters/RecordTypeAdapter.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'source' in 'Expression IMemberModelEx.GetExpression(Expression source)'.
var ParamLambdaVar2 = destMemberVar2;
if(member.DestinationMember.Type.IsRecordType())
ParamLambdaVar2 = arg.Context.Config.CreateMapInvokeExpressionBody(member.Getter.Type, member.DestinationMember.Type, destMemberVar2);
Expand Down
Loading