简体   繁体   English

如何使用AutoMapper创建复杂的映射?

[英]How to create complex mapping using AutoMapper?

I have the following Entity-Models 我有以下实体模型

public class Blog 
{
    public int Id { get; set;}
    public string Title { get; set; }
    public string Body { get; set; }

    [ForeignKey("Category")]
    public int? CategoryId { get; set; }
    public virtual Category Category { get; set; }
    public virtual ICollection<Comment> Comments { get; set; }
}

public class Category
{
    public int Id { get; set;}

    public string Name { get; set; }
}

public class Comment
{
    public int Id { get; set;}

    public string Title { get; set; }
    public string Body { get; set; }
    [ForeignKey("Blog")]
    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; }
}

Then I have the following view-model in which I like to tell AutoMapper to map the Blog object into the BlogViewModel notice the CategoryName property will need to come from Blog.Category.Name and each Comment in the Blog.Comments need to be converter to CommentViewModel using the organic convention. 然后,我有以下视图模型中,我想告诉AutoMapper映射Blog对象为BlogViewModel通知的CategoryName属性将需要来自Blog.Category.Name每个CommentBlog.Comments必须转换器CommentViewModel使用有机约定。

I currently set the mapping at run time using reflection for any class that implements the ICustomMap interface. 我目前在运行时使用反射为实现ICustomMap接口的任何类设置映射。 Please read the comment in the code over the Transfer(IMapper mapper) method. 请通过Transfer(IMapper mapper)方法阅读代码中的注释。

public class BlogViewModel : ICustomMapFrom 
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public string MyCatName { get; set; }
    public IEnumerable<CommentViewModel> Comments { get; set; }

    // **IMPORTANT NOTE**
    // This method is called using reflection when the on Application_Start() method.
    // If IMapper is the wrong Interface to pass, I can change
    // the implementation of ICustomMap
    // I assumed that `IMapper` is what is needed to add configuration at runtime.
    public void Transfer(IConfigurationProvider config)
    {
        // How to I do the custom mapping for my MyCatName and Comments?
        // I need to use the config to create the complex mapping
        // AutoMapper.Mapper.Map(typeof(Blog), typeof(BlogViewModel));
    }
}

Finally here is my CommentViewModel 最后是我的CommentViewModel

public class CommentViewModel : IMapFrom<Comment>
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
}

How can I tell AutoMapper how to map the CategoryName and the Comments? 如何告诉AutoMapper如何映射CategoryName和Comments?

Updated 更新

Here is how I would create the mapping. 这是我创建映射的方式。 I would have the following 3 interfaces 我将有以下3个界面

public interface IMap
{
}

public interface IMapFrom<T> : IMap
{
}

public interface ICustomMapFrom : IMap
{
    void Map(IConfigurationProvider config);
}

Then in the Global.cs file 然后在Global.cs文件中

I would execute the Run method on startup. 我会在启动时执行Run方法。 Basically this method will scan assemblies and register the classes that I would want to register using the interfaces. 基本上,此方法将扫描程序集并使用接口注册我要注册的类。

public class ConfigureAutoMapper 
{
    public void Run()
    {
        var types = AssemblyHelpers.GetInternalAssemblies()
                                   .SelectMany(x => x.GetTypes())
                                   .Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && typeof(IMap).IsAssignableFrom(x))
                                   .ToList();


        RegisterStandardMappings(types);
        RegisterCustomMappings(types);
    }

    private static void RegisterStandardMappings(IEnumerable<Type> types)
    {
        foreach (Type type in types)
        {
            if(type.IsGenericType && typeof(IMapFrom<>).IsAssignableFrom(type))
            {
                AutoMapper.Mapper.Map(type.GetGenericArguments()[0], type);
            }
        }
    }

    private static void RegisterCustomMappings(IEnumerable<Type> types)
    {
        foreach (Type type in types)
        {
            if (typeof(ICustomMapFrom).IsAssignableFrom(type))
            {
                ICustomMapFrom map = (ICustomMapFrom)Activator.CreateInstance(type);
                var t = AutoMapper.Mapper.Configuration;

                map.Map(Mapper.Configuration);
            }
        }
    }
}

I wrote an NUnit test which sets up AutoMapper with your classes. 我编写了一个NUnit测试,该测试使用您的类设置了AutoMapper。 AutoMapper supports the mapping of CategoryName out of the box. AutoMapper支持开箱即用的CategoryName映射。

[TestFixture]
public class TestClass
{
    [Test]
    public void Test1()
    {
        var config = new MapperConfiguration(cfg =>
        {
            cfg.CreateMap<Blog, BlogViewModel>();
        });

        config.AssertConfigurationIsValid();

        var blog = new Blog()
        {
            Body = "Blog body",
            Category = new Category { Name = "My Category" },
            Comments = new List<Comment>() {
                new Comment { Body = "Comment body 1" },
                new Comment { Body = "Comment body 2" }
            }
        };

        var mapper = config.CreateMapper();
        var result = mapper.Map<Blog, BlogViewModel>(blog);

        Assert.AreEqual(blog.Body, "Blog body");
        Assert.AreEqual(blog.Category.Name, result.CategoryName);
        List<CommentViewModel> comments = result.Comments.ToList();
        Assert.That(comments.Any(c => c.Body == "Comment body 1"));
        Assert.That(comments.Any(c => c.Body == "Comment body 2"));
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM