如何使用Mapster创建可重用的映射配置文件?

4
我有一个 .Net 5 的 Web Api 项目,想要使用 Mapster v7.2.0 来避免手动映射对象。以下代码展示了一个简单的场景:
- 设置映射配置 - 从多个源映射 - 将不同名称的字段映射到目标
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    [HttpGet]
    public ActionResult<UsernameWithTodoTitle> Get()
    {
        TypeAdapterConfig<(User, Todo), UsernameWithTodoTitle>
            .NewConfig()
            .Map(dest => dest, src => src.Item1) // map everything from user
            .Map(dest => dest, src => src.Item2) // map everything from todo
            .Map(dest => dest.TodoTitle, src => src.Item2.Title); // map the special fields from todo
        
        var user = new User { Username = "foo", FieldFromUser = "x" };
        var todo = new Todo { Title = "bar", FieldFromTodo = "y" };
        
        var usernameWithTodoTitle = (user, todo).Adapt<(User, Todo), UsernameWithTodoTitle>();
        
        return Ok(usernameWithTodoTitle);
    }
}

public class User
{
    public string Username { get; set; }
    public string FieldFromUser { get; set; }
}

public class Todo
{
    public string Title { get; set; } // !! map this one to the TodoTitle field !!
    public string FieldFromTodo { get; set; }
}

public class UsernameWithTodoTitle
{
    public string Username { get; set; }
    public string TodoTitle { get; set; } // !! this one is special, is has a different name !!
    public string FieldFromUser { get; set; }
    public string FieldFromTodo { get; set; }
}

运行应用程序时,这种映射方式似乎很好地起作用了。
我不得不以这种方式设置配置,其他方式对我来说不起作用。但是还有三件事情需要解决:
1.配置看起来对我来说似乎不正确。它将所有任务映射到一个 TODO 中,并再次映射特殊字段...所以它可能会多次循环遍历?如果存在多个名称不同的字段,则这可能会变得昂贵。
2.我在控制器内创建了配置。如何创建一个可重复使用的映射配置类并全局注册?
3.当有一个映射配置文件时,这一行代码 `var usernameWithTodoTitle = (user, todo).Adapt<(User, Todo), UsernameWithTodoTitle>();` 看起来很凌乱。更好的方式是 `var usernameWithTodoTitle = UsernameWithTodoTitle.Adapt((user, todo)) /* 作为元组传递 */`,因为它根据参数类型选择正确的映射配置文件。
你们有什么想法如何创建这样一个映射配置文件吗?

你能添加你想要的Automapper示例吗? - Ali Zeinali
@AliZeinali 抱歉,只是想澄清一下 => 我正在使用Mapster而不是Automapper。 Automapper无法从多个源映射。https://dev59.com/cGEi5IYBdhLWcg3wRayf - Question3r
我添加了一个伪代码实现……希望能对你有所帮助。 - Question3r
4个回答

4
我使用Mapster完成了它。我的做法是在Startup.cs中:
public void ConfigureServices(IServiceCollection services)
{
   // Some other magical code

   // Tell Mapster to scan this assambly searching for the Mapster.IRegister
   // classes and execute them
   TypeAdapterConfig.GlobalSettings.Scan(Assembly.GetExecutingAssembly());
}

创建另一个类如下:

using Mapster;

namespace Your.Cool.Namespace
{
    public class MappingConfig : IRegister
    {
        public void Register(TypeAdapterConfig config)
        {
            // Put your mapping logic here
            config
                .NewConfig<MySourceType, MyDestinyType>()
                .Map(dest => dest.PropA, src => src.PropB);
        }
    }
}

重要的部分是使用TypeAdapterConfig.GlobalSettings,它是Mapster用于保存映射配置的静态公共单例。如果按照Jack的建议去做,会创建一个全新的TypeAdapterConfig而不是Mapster实际使用的那个,这样就无法正常工作(至少对我来说是这样)。
在您的单元测试中,请记得加载映射配置文件。
[AssemblyInitialize] // Magic part 1 ~(˘▾˘~)
public static void AssemblyInitialization(TestContext testContext)
{
    // Magic part 2 (~˘▾˘)~
    TypeAdapterConfig.GlobalSettings.Scan(AppDomain.CurrentDomain.GetAssemblies());
}

3

更新:找不到使用Mapster实现您要尝试的操作的方法,但是这里有一个使用Automapper运作的例子。

using AutoMapper;
using System;

namespace ConsoleApp5
{
    class A { public string FirstName { get; set; } }

    public class B { public string Address1 { get; set; } }

    public class C
    {
        public string FirstName { get; set; }
        public string Address1 { get; set; }
    }

    public class DemoProfile : Profile
    {
        public DemoProfile()
        {
            CreateMap<(A, B), C>()
                .ForMember(dest=> dest.FirstName, opts => opts.MapFrom(src => src.Item1.FirstName))
                .ForMember(dest => dest.Address1, opts => opts.MapFrom(src => src.Item2.Address1));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var config = new MapperConfiguration(cfg => {
                cfg.AddProfile<DemoProfile>();
            });

            var mapper = config.CreateMapper();
            var destination = mapper.Map<C>((new A {  FirstName = "Test" }, new B { Address1 = "Addr" }));

            Console.ReadKey();
        }
    }
}

嘿,我之前从未使用过Mapster,但是这是我收集到的信息。它非常特定于您使用的元组类型Tuple<T1,T2>而不是(T1,T2),但除此之外,我能够轻松运行和映射它而没有问题。以下是一个小的终端示例。

using Mapster;
using System;

namespace ConsoleApp5
{
    class A { public string FirstName { get; set; } }

    public class B { public string Address1 { get; set; } }

    public class C
    {
        public string FirstName { get; set; }
        public string Address1 { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Option 1
            TypeAdapterConfig<Tuple<A, B>, C>.NewConfig()
                .Map(dest => dest.FirstName, src => src.Item1.FirstName)
                .Map(dest => dest.Address1, src => src.Item2.Address1);

            var destObject = new Tuple<A, B>(new A { FirstName = "Test" }, new B { Address1 = "Address 1" })
                .Adapt<Tuple<A, B>, C>();

            // Option 2
            TypeAdapterConfig<(A, B), C>.NewConfig()
                .Map(dest => dest.FirstName, src => src.Item1.FirstName)
                .Map(dest => dest.Address1, src => src.Item2.Address1);

            var destObject2 = (new A { FirstName = "Test" }, new B { Address1 = "Address 1" })
                .Adapt<(A, B), C>();

            Console.ReadKey();
        }
    }
}

谢谢你的帮助。我尝试了一下并更新了我的问题,但是还有一些问题需要解决... - Question3r
我在他们的文档中没有找到类似Automapper中的配置文件的参考,但是您可以创建一个基类或接口,并编写一个扩展程序在启动时加载它们。 - Felipe Ramos
已为Automapper添加了有关profile和tuple的参考,希望这能有所帮助。我认为您可能可以使用Mapster来完成它,但这可能需要更多的工作。 - Felipe Ramos
不完全是我想要的,但没关系 :) 我根据你的答案为给定的问题发布了一个解决方案 https://dev59.com/GsHqa4cB1Zd3GeqP79PI#68973616 - Question3r

0

基于@Felipe Ramos的答案,我无法使用Mapster解决它,但是可以使用Automapper。这是我的解决方案,仅供完整性考虑。如果有Mapster的解决方案,请告诉我!

我安装了以下包

AutoMapper v10.1.1

AutoMapper.Extensions.Microsoft.DependencyInjection v8.1.1

在方法Startup.ConfigureServices中,我添加了一行services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

整个代码如下

[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    private readonly IMapper _mapper;
    
    public MyController(IMapper mapper)
    {
        _mapper = mapper;
    }
    
    [HttpGet]
    public ActionResult<UsernameWithTodoTitle> Get()
    {
        var user = new User { Username = "foo", FieldFromUser = "x" };
        var todo = new Todo { Title = "bar", FieldFromTodo = "y" };
        var usernameWithTodoTitle = _mapper.Map<UsernameWithTodoTitle>((user, todo));
    
        return Ok(usernameWithTodoTitle);
    }
}

public class User
{
    public string Username { get; set; }
    public string FieldFromUser { get; set; }
}

public class Todo
{
    public string Title { get; set; } // !! map this one to the TodoTitle field !!
    public string FieldFromTodo { get; set; }
}

public class UsernameWithTodoTitle
{
    public string Username { get; set; }
    public string TodoTitle { get; set; } // !! this one is special, is has a different name !!
    public string FieldFromUser { get; set; }
    public string FieldFromTodo { get; set; }
}

public class UsernameWithTodoTitleMappingProfile : Profile
{
    public UsernameWithTodoTitleMappingProfile()
    {
        CreateMap<(User, Todo), UsernameWithTodoTitle>()
            .ForMember(
                destination => destination.Username,
                memberOptions => memberOptions.MapFrom(source => source.Item1.Username))
            .ForMember(
                destination => destination.TodoTitle,
                memberOptions => memberOptions.MapFrom(source => source.Item2.Title))
            .ForMember(
                destination => destination.FieldFromUser,
                memberOptions => memberOptions.MapFrom(source => source.Item1.FieldFromUser))
            .ForMember(
                destination => destination.FieldFromTodo,
                memberOptions => memberOptions.MapFrom(source => source.Item2.FieldFromTodo));
    }
}

0

你可以使用下面的方法:

    var config = new TypeAdapterConfig()
    {
        RequireExplicitMapping = true,
        RequireDestinationMemberSource = true,
        Compiler = exp => exp.CompileFast()
    };

    config.Scan("Your assembly");

    services.AddSingleton(config);
    services.AddTransient<IMapper, ServiceMapper>();

    public class RegisterConfig : IRegister
    {
        public void Register(TypeAdapterConfig config)
        {
            config.NewConfig<TSource, TDestination>();
        }
    }

其中 services 是 IServiceCollection


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接