Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
623f940
add Ignor Ctor Param
DocSvartz Jan 21, 2025
5e9b8df
add Test
DocSvartz Jan 21, 2025
0685bb3
refactoring algorithm
DocSvartz Jan 22, 2025
fdf059a
add supporting Ignored() to readonly interface
DocSvartz Jan 22, 2025
6cf1320
add test Interface Ctor param Ignored()
DocSvartz Jan 22, 2025
7f6cdde
fix misprints
DocSvartz Jan 22, 2025
0f2302a
fix maToTarget behavior From RecordType
DocSvartz Jan 23, 2025
f8469e9
add To RecordTypeAdapter
DocSvartz Jan 23, 2025
d02756f
fix mapToTarget test from Record
DocSvartz Jan 23, 2025
15e710d
merge
DocSvartz Jan 24, 2025
564da6d
fix map to target from recordType
DocSvartz Jan 24, 2025
f29d935
update RecordType Tests
DocSvartz Jan 25, 2025
0c5384d
refactoring Map to target record
DocSvartz Jan 28, 2025
f5a3ce6
Fix error with Using Destination Value From Record Type
DocSvartz Jan 28, 2025
5c2d4a7
add Null check destination from mapToTarget RecordType
DocSvartz Feb 2, 2025
32f0a34
add support from RecordType
DocSvartz Feb 2, 2025
0711eb8
add Test for RecordType check
DocSvartz Feb 2, 2025
892c42f
fix null Check
Feb 3, 2025
23a5089
fix IgnoreNullValues for RecordType when used Map mapping
DocSvartz Feb 3, 2025
5a5623b
FIx RequiredProperty
DocSvartz Feb 26, 2025
1cba2fd
Temporary disable for RecordType
DocSvartz Feb 26, 2025
8d1c7cb
fix MapToTarget Polymorphic mapping to null value Property
DocSvartz May 2, 2025
d198ef2
add support using MapWith as param in UseDestinatonValue
DocSvartz Jan 19, 2025
49576a5
refactoring test
DocSvartz Jan 19, 2025
dbd6f37
update algoritm
DocSvartz Jan 20, 2025
343874f
Fix Poco Detection for Class
DocSvartz Jan 26, 2025
9a210bd
add RequireExplicitMappingPrimitive config
DocSvartz Mar 22, 2025
ea2da89
add Test for RequireExplicitMappingPrimitive
DocSvartz Mar 22, 2025
2986140
add EFCoreProjectToType and base test
Apr 7, 2025
0e27928
Fix Mapping Hidden Base member
DocSvartz Apr 13, 2025
a00dfa8
Add support .Inherits() and .Include() to map OpenGeneric
DocSvartz Apr 12, 2025
d75579d
IgnoreNonMapped skip RequireDestinationMemberSource
DocSvartz Jan 23, 2025
3553d25
IgnoreNonMapped mark not mapping memeber as Ignore
DocSvartz Apr 14, 2025
f647867
NullableExpressionVisitor improvement
DocSvartz Apr 9, 2025
4ace877
add Simply Async mapping
DocSvartz Jun 13, 2025
444fedf
Polymorfing Map toTarget
DocSvartz Jun 17, 2025
3653ba4
fix merge
DocSvartz Jun 17, 2025
d23b2b7
fix IgnoreNullValues for RecordType when used Map mapping
DocSvartz Feb 3, 2025
2de0109
Fix Name Ctor param Matching
DocSvartz Feb 11, 2025
a70694c
fix IgnoreNullValues for RecordType when used Map mapping
DocSvartz Jun 17, 2025
c8955bd
Merge pull request #814 from DocSvartz/Alpha-9-1
DocSvartz Sep 30, 2025
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
17 changes: 17 additions & 0 deletions src/Mapster.Async.Tests/AsyncTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using MapsterMapper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;

Expand Down Expand Up @@ -87,6 +88,22 @@ public async Task NestedAsync()
dtoOwnership.Owner.Name.ShouldBe("John Doe");
}

[TestMethod]
public async Task SimplyAsync()
{
TypeAdapterConfig<Poco, Dto>.NewConfig()
.AfterMappingAsync(async dest => { dest.Name = await GetName(); });

var poco = new Poco { Id = "foo" };
var dto = await poco.AdaptAsync<Dto>();
dto.Name.ShouldBe("bar");

IMapper instance = new Mapper();

var destination = await instance.MapAsync<Dto>(poco);
destination.Name.ShouldBe("bar");
}

private static async Task<string> GetName()
{
await Task.Delay(1);
Expand Down
42 changes: 41 additions & 1 deletion src/Mapster.Async/TypeAdapterExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using MapsterMapper;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

Expand Down Expand Up @@ -101,5 +102,44 @@ public static async Task<TDestination> AdaptToAsync<TDestination>(this IAdapterB
}
}


/// <summary>
/// Map asynchronously to destination type.
/// </summary>
/// <typeparam name="TDestination">Destination type to map.</typeparam>
/// <param name="builder"></param>
/// <returns>Type of destination object that mapped.</returns>
public static async Task<TDestination> AdaptAsync<TDestination>(this object? source)
{
return await source.BuildAdapter().AdaptToTypeAsync<TDestination>();
}


/// <summary>
/// Map asynchronously to destination type.
/// </summary>
/// <typeparam name="TDestination">Destination type to map.</typeparam>
/// <param name="builder"></param>
/// <param name="config">Configuration</param>
/// <returns>Type of destination object that mapped.</returns>
public static async Task<TDestination> AdaptAsync<TDestination>(this object? source, TypeAdapterConfig config)
{
return await source.BuildAdapter(config).AdaptToTypeAsync<TDestination>();
}

}

public static class IMapperAsyncExtentions
{
/// <summary>
/// Map asynchronously to destination type.
/// </summary>
/// <typeparam name="TDestination">Destination type to map.</typeparam>
/// <param name="builder"></param>
/// <returns>Type of destination object that mapped.</returns>
public static async Task<TDestination> MapAsync<TDestination>(this IMapper mapper, object? source)
{
return await mapper.From(source).AdaptToTypeAsync<TDestination>();
}
}
}
11 changes: 11 additions & 0 deletions src/Mapster.Core/Enums/ProjectToTypeAutoMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Mapster.Enums
{
public enum ProjectToTypeAutoMapping
{
AllTypes = 0,
WithoutCollections = 1,
OnlyPrimitiveTypes = 2,
}
}
49 changes: 49 additions & 0 deletions src/Mapster.Core/Utils/ProjectToTypeVisitors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Linq.Expressions;

namespace Mapster.Utils
{
public sealed class TopLevelMemberNameVisitor : ExpressionVisitor
{
public string? MemeberName { get; private set; }

public override Expression Visit(Expression node)

Check warning on line 10 in src/Mapster.Core/Utils/ProjectToTypeVisitors.cs

View workflow job for this annotation

GitHub Actions / build

Nullability of type of parameter 'node' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 10 in src/Mapster.Core/Utils/ProjectToTypeVisitors.cs

View workflow job for this annotation

GitHub Actions / build

Nullability of type of parameter 'node' doesn't match overridden member (possibly because of nullability attributes).
{
if (node == null)
return null;

Check warning on line 13 in src/Mapster.Core/Utils/ProjectToTypeVisitors.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.

Check warning on line 13 in src/Mapster.Core/Utils/ProjectToTypeVisitors.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.
switch (node.NodeType)
{
case ExpressionType.MemberAccess:
{
if (string.IsNullOrEmpty(MemeberName))
MemeberName = ((MemberExpression)node).Member.Name;

return base.Visit(node);
}
}

return base.Visit(node);
}
}

public sealed class QuoteVisitor : ExpressionVisitor
{
public List<UnaryExpression> Quotes { get; private set; } = new();

public override Expression Visit(Expression node)

Check warning on line 33 in src/Mapster.Core/Utils/ProjectToTypeVisitors.cs

View workflow job for this annotation

GitHub Actions / build

Nullability of type of parameter 'node' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 33 in src/Mapster.Core/Utils/ProjectToTypeVisitors.cs

View workflow job for this annotation

GitHub Actions / build

Nullability of type of parameter 'node' doesn't match overridden member (possibly because of nullability attributes).
{
if (node == null)
return null;

Check warning on line 36 in src/Mapster.Core/Utils/ProjectToTypeVisitors.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.

Check warning on line 36 in src/Mapster.Core/Utils/ProjectToTypeVisitors.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.
switch (node.NodeType)
{
case ExpressionType.Quote:
{
Quotes.Add((UnaryExpression)node);
return base.Visit(node);
}
}

return base.Visit(node);
}
}
}
27 changes: 24 additions & 3 deletions src/Mapster.EFCore.Tests/EFCoreTest.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mapster.EFCore.Tests.Models;
using MapsterMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Mapster.EFCore.Tests
{
Expand Down Expand Up @@ -67,6 +67,27 @@ public void MapperInstance_From_OrderBy()
var last = orderedQuery.Last();
last.LastName.ShouldBe("Olivetto");
}

[TestMethod]
public void MergeIncludeWhenUsingEFCoreProjectToType()
{
var options = new DbContextOptionsBuilder<SchoolContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString("N"))
.Options;
var context = new SchoolContext(options);
DbInitializer.Initialize(context);

var mapsterInstance = new Mapper();

var query = context.Students
.Include(x => x.Enrollments.OrderByDescending(x => x.StudentID).Take(1))
.EFCoreProjectToType<StudentDto>();

var first = query.First();

first.Enrollments.Count.ShouldBe(1);
first.LastName.ShouldBe("Alexander");
}
}

public class StudentDto
Expand Down
96 changes: 96 additions & 0 deletions src/Mapster.EFCore/EFCoreExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using Mapster.Enums;
using Mapster.Models;
using Mapster.Utils;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;

namespace Mapster.EFCore
{
public static class EFCoreExtensions
{
public static IQueryable<TDestination> EFCoreProjectToType<TDestination>(this IQueryable source,
TypeAdapterConfig? config = null, ProjectToTypeAutoMapping autoMapConfig = ProjectToTypeAutoMapping.WithoutCollections)
{
var allInclude = new IncludeVisitor();
allInclude.Visit(source.Expression);

if (config == null)
{
config = TypeAdapterConfig.GlobalSettings
.Clone()
.ForType(source.ElementType, typeof(TDestination))
.Config;

var mapTuple = new TypeTuple(source.ElementType, typeof(TDestination));

TypeAdapterRule rule;
config.RuleMap.TryGetValue(mapTuple, out rule);

if(rule != null)
{
rule.Settings.ProjectToTypeMapConfig = autoMapConfig;

foreach (var item in allInclude.IncludeExpression)
{
var find = rule.Settings.Resolvers.Find(x => x.SourceMemberName == item.Key);
if (find != null)
{
find.Invoker = (LambdaExpression)item.Value.Operand;
find.SourceMemberName = null;
}
else
rule.Settings.ProjectToTypeResolvers.TryAdd(item.Key, item.Value);
}
}
}
else
{
config = config.Clone()
.ForType(source.ElementType, typeof(TDestination))
.Config;
}

return source.ProjectToType<TDestination>(config);
}
}


internal class IncludeVisitor : ExpressionVisitor
{
public Dictionary<string, UnaryExpression> IncludeExpression { get; protected set; } = new();
private bool IsInclude(Expression node) => node.Type.Name.StartsWith("IIncludableQueryable");

[return: NotNullIfNotNull("node")]
public override Expression Visit(Expression node)
{
if (node == null)
return null;

switch (node.NodeType)
{
case ExpressionType.Call:
{
if (IsInclude(node))
{
var QuoteVisiter = new QuoteVisitor();
QuoteVisiter.Visit(node);

foreach (var item in QuoteVisiter.Quotes)
{
var memberv = new TopLevelMemberNameVisitor();
memberv.Visit(item);

IncludeExpression.TryAdd(memberv.MemeberName, item);
}
}
return base.Visit(node);
}
}

return base.Visit(node);
}
}

}
2 changes: 1 addition & 1 deletion src/Mapster.Immutable/ImmutableAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
return Expression.Empty();
}

protected override Expression CreateInlineExpression(Expression source, CompileArgument arg)
protected override Expression CreateInlineExpression(Expression source, CompileArgument arg, bool IsRequiredOnly = false)
{
return CreateInstantiationExpression(source, arg);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Mapster.JsonNet/JsonAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio
throw new System.NotImplementedException();
}

protected override Expression CreateInlineExpression(Expression source, CompileArgument arg)
protected override Expression CreateInlineExpression(Expression source, CompileArgument arg, bool IsRequiredOnly = false)
{
throw new System.NotImplementedException();
}
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 @@ -7,6 +7,7 @@
<AssemblyOriginatorKeyFile>Mapster.Tests.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<LangVersion>11.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
Expand Down
53 changes: 53 additions & 0 deletions src/Mapster.Tests/WhenExplicitMappingRequired.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class WhenExplicitMappingRequired
public void TestCleanup()
{
TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = false;
TypeAdapterConfig.GlobalSettings.RequireExplicitMappingPrimitive = false;
TypeAdapterConfig.GlobalSettings.Clear();
}

Expand Down Expand Up @@ -140,8 +141,60 @@ public void UnmappedChildPocoShouldFailed()
setter.Compile(); // Should fail here
}

[TestMethod]
public void RequireExplicitMappingPrimitiveWork()
{
TypeAdapterConfig.GlobalSettings.RequireExplicitMappingPrimitive = true;

TypeAdapterConfig<Source783, Destination783>.NewConfig();

Should.Throw<CompileException>(() =>
{
TypeAdapterConfig.GlobalSettings.Compile(); // throw CompileException
});

byte byteSource = 10;

byteSource.Adapt<byte>(); // Should work when the type is mapped to itself

Should.Throw<CompileException>(() =>
{
byteSource.Adapt<int>(); // throw CompileException, Do not map to another primitive type without registering the configuration
});

Should.NotThrow(() =>
{
TypeAdapterConfig<byte, int>.NewConfig();

byteSource.Adapt<int>(); // Not throw CompileException when config is registering
});

Should.NotThrow(() =>
{
TypeAdapterConfig<Source783, Destination783>.NewConfig()
.Map(dest=> dest.MyProperty, src=> int.Parse(src.MyProperty));
// it work works because int.Parse return Type Int. Type is mapped to itself (int -> int) without config.

var sourceMapconfig = new Source783() { MyProperty = "128" };
var resultMapconfig = sourceMapconfig.Adapt<Destination783>();

resultMapconfig.MyProperty.ShouldBe(128);
});

}


#region TestClasses

public class Source783
{
public string MyProperty { get; set; } = "";
}

public class Destination783
{
public int MyProperty { get; set; }
}

public enum NameEnum
{
Expand Down
Loading
Loading