AutoMapper是否也可以用作重塑形状的映射工具?

本文关键字:映射 工具 也可以 是否 AutoMapper | 更新日期: 2023-09-27 18:01:23

我知道AutoMapper只是映射两个对象之间的属性。

Automapper不是用来重塑数据的,在我看来,它必须被编程。

我有一个需要重构成PeriodListViewModel.List<CellViewModel>PeriodDTO.IEnumerable<Period>

每个CellViewModel容纳一个List<RowViewModel>

这不是1对1的映射我想也许这个不应该被映射-因为这是不可能的-

public class PeriodRequest
    {
        public IEnumerable<Period> Periods { get; set; }
        public IEnumerable<DateTime> Weeks { get; set; }
    }
public class PeriodListViewModel
{       
    public PeriodListViewModel(IEnumerable<Period> periods)
    {
        CellViewModels = new List<CellViewModel>();
        var groupedPeriods = periods.GroupBy(p => p.LessonNumber).OrderBy(p => p.Key).ToList();
        foreach (var groupedPeriod in groupedPeriods)
        {
            var cellViewModel = new CellViewModel();
            CellViewModels.Add(cellViewModel);
            foreach (var period in groupedPeriod)
            {
                var rowViewModel = new RowViewModel();
                rowViewModel.Content = period.Content;
                rowViewModel.SchoolclassCode = period.SchoolclassCode;
                rowViewModel.DayName = period.LessonDate.ToShortDateString();
                rowViewModel.DayIndex = (int)period.LessonDate.DayOfWeek;
                rowViewModel.LessonNumber = period.LessonNumber;
                cellViewModel.Rows.Add(rowViewModel);
            }
        }
    }
    public List<CellViewModel> CellViewModels { get; set; }
}
public class CellViewModel
    {
        public CellViewModel()
        {
            Rows = new List<RowViewModel>();
        }
        public List<RowViewModel> Rows { get; set; }
    }
public class RowViewModel
    {
        public string DayName { get; set; }
        public string SchoolclassCode { get; set; }
        public string Content { get; set; }
        public int DayIndex { get; set; }
        public int LessonNumber { get; set; }
    }
public class Period
    {
        public int PeriodId { get; set; }
        public DateTime LessonDate { get; set; }
        public int LessonNumber { get; set; }
        public string SchoolclassCode { get; set; }
        public string Content { get; set; }
        public int SchoolyearId { get; set; }
        public Schoolyear Schoolyear { get; set; }
...
}

目前,可以很容易地对PeriodListViewModel进行单元测试。我现在在问自己,AutoMapper怎样才能让情况变得更好?

AutoMapper是否也可以用作重塑形状的映射工具?

虽然您没有从IEnumerable<Period>List<CellViewModel>的琐碎1:1映射,但您仍然可以通过仅在您获得类对类联系的地方使用Automapper来获得一些值,在您的示例中是在PeriodRowViewModel之间。

考虑到这一点,你也可以更好地利用LINQ,并在一个清扫运动中完成所有工作,只要你设置需要从PeriodRowViewModel重新塑造的投影。

下面是一个控制台代码示例,它准确地演示了这一点。请注意,我们正在使用Automapper的解析器类的概念,通过ValueResolve<TSource,TDestination>进行重新塑造,并且我在CellViewModel中添加了一个额外的构造函数来接受IEnumerable<RowViewModel>,这有助于改进我们的声明。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AutoMapper;
namespace GroupingMapping
{
    public class PeriodRequest
    {
        public IEnumerable<Period> Periods { get; set; }
        public IEnumerable<DateTime> Weeks { get; set; }
    }
    public class RowViewModel
    {
        public string DayName { get; set; }
        public string SchoolclassCode { get; set; }
        public string Content { get; set; }
        public int DayIndex { get; set; }
        public int LessonNumber { get; set; }
    }
    public class Period
    {
        public int PeriodId { get; set; }
        public DateTime LessonDate { get; set; }
        public int LessonNumber { get; set; }
        public string SchoolclassCode { get; set; }
        public string Content { get; set; }
        public int SchoolyearId { get; set; }
        // No definition provided for Schoolyear
        //public Schoolyear Schoolyear { get; set; }
    }
    public class CellViewModel
    {
        public CellViewModel()
        {
            Rows = new List<RowViewModel>();
        }
        public CellViewModel(IEnumerable<RowViewModel> rowVMSet)
        {
            Rows = new List<RowViewModel>(rowVMSet);
        }
        public List<RowViewModel> Rows { get; set; }
    }
    public class PeriodListViewModelEx
    {
        public PeriodListViewModelEx(IEnumerable<Period> periods)
        {
            CellViewModels = new List<CellViewModel>(periods
                .GroupBy(p => p.LessonNumber)
                .OrderBy(grp => grp.Key)
                .Select(grp =>
                {
                    return new CellViewModel(
                        grp.Select(p => { return Mapper.Map<Period, RowViewModel>(p); }));
                }));
        }
        public List<CellViewModel> CellViewModels { get; set; }
    }
    class DateTimeToDateNameResolver : ValueResolver<DateTime, string>
    {
        protected override string ResolveCore(DateTime source)
        {
            return source.ToShortDateString();
        }
    }
    class DateTimeToDayOfWeekResolver : ValueResolver<DateTime, int>
    {
        protected override int ResolveCore(DateTime source)
        {
            return (int)source.DayOfWeek;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Mapper.CreateMap<Period, RowViewModel>()
                .ForMember(dest => dest.DayName, opt => opt.ResolveUsing<DateTimeToDateNameResolver>().FromMember(src => src.LessonDate))
                .ForMember(dest => dest.DayIndex, opt => opt.ResolveUsing<DateTimeToDayOfWeekResolver>().FromMember(src => src.LessonDate));
            Period[] periods = new Period[3];
            periods[0] = new Period { PeriodId = 1, LessonDate = DateTime.Today.Add(new TimeSpan(1, 0, 0, 0)), LessonNumber = 101, SchoolclassCode = "CS101", Content = "Intro to CS", SchoolyearId = 2013 };
            periods[1] = new Period { PeriodId = 2, LessonDate = DateTime.Today.Add(new TimeSpan(2, 0, 0, 0)), LessonNumber = 101, SchoolclassCode = "CS101", Content = "Intro to CS", SchoolyearId = 2013 };
            periods[2] = new Period { PeriodId = 3, LessonDate = DateTime.Today.Add(new TimeSpan(1, 0, 0, 0)), LessonNumber = 102, SchoolclassCode = "EN101", Content = "English (I)", SchoolyearId = 2013 };
            PeriodListViewModelEx pvModel = new PeriodListViewModelEx(periods);
            Console.WriteLine("CellViews: {0}", pvModel.CellViewModels.Count);
            foreach (CellViewModel cvm in pvModel.CellViewModels)
            {
                Console.WriteLine("{0} items in CellViewModel Group", cvm.Rows.Count);
            }
            Console.WriteLine("Inspecting CellViewModel Rows");
            foreach (CellViewModel cvm in pvModel.CellViewModels)
            {
                foreach (RowViewModel rvm in cvm.Rows)
                {
                    Console.WriteLine("  DayName: {0}", rvm.DayName);
                    Console.WriteLine("  SchoolclassCode: {0}", rvm.SchoolclassCode);
                    Console.WriteLine("  Content: {0}", rvm.Content);
                    Console.WriteLine("  DayIndex: {0}", rvm.DayIndex);
                    Console.WriteLine("  LessonNumber: {0}", rvm.LessonNumber);
                    Console.WriteLine("  -");
                }
                Console.WriteLine("--");
            }
            Console.ReadKey();
        }
    }
}

几个月来,我主要使用AutoMapper从我的实体模型创建视图模型,基本上就是你在那里做的事情。我已经使用AutoMapper来平和反平非常复杂的模型。

我个人不会在视图模型的构造函数中这样做。原因是您可能有不同的PeriodListViewModel源。这个想法是,一旦您创建/配置了映射器,您就可以根据情况需要执行Mapper.Map<PeriodViewModel(collectionOfCells);Mapper.Map<PeriodViewModel(periodEntity);这样,你的PeriodViewModels和Period类都没有任何知识或相互关系。如果Period类响应数据库的更改而更改,那么您只需调整自动映射器映射以适应更改,保持PeriodViewModel类不变,因此不必触及使用它的任何视图。反之亦然。

即使我不使用automapper,我仍然会有静态助手方法来复制实体模型到视图模型。像视图模型这样简单的东西应该有一个简单的默认构造函数,以便于使用。

然而,我要说的是,在AutoMapper中执行复杂的映射是相当具有挑战性的,并且调试有时令人生畏。很多时候,我发现自己在另一个开发人员的办公室里帮助他们理解正在发生的映射链。顾名思义,当它深入研究子对象/集合/导航属性时,它会自动映射它们,因此许多事情都隐式地发生,这确实很难调试。你基本上必须对这些对象图进行大量的心理可视化,然后去寻找你认为正在使用的映射。有些映射选项不允许多行匿名方法,因此您不能放置断点或额外的日志代码。

作为一名经常将数据从数据库推送到前端的开发人员,AutoMapper几乎正是我一直梦想的,但在使用它几个月后,我可能会避免使用它来翻译超过2层深度的对象图。对于你在例子中所做的,我认为AutoMapper就可以了。

更新:

我会保持查询和映射的分离。第一个查询返回我的实体,因为我可能还需要向where条件传递参数。然后将ToList生成的List传递给automapper。map将Period实体转换为PeriodViewModel的。在您的示例中,由于组by,您有一个嵌套集合。你可以创建一个从周期集合(groupedPeriod)到单个CellViewModel的映射,然后

foreach (var groupOfPeriods in groupedPeriods)
{
            Mapper.Map<CellViewModel>(groupOfPeriods );
            CellViewModels.Add(cellViewModel);
}

我相信这将工作(给它创建一个映射,因为一个igrouing实现IEnumerable,在这种情况下,element是一个Period)。

另一个选项,不是创建从IEnumerable到CellViewModel的映射,而是创建从Period到RowViewModel的映射,并这样做:

...
foreach (var groupedPeriod in groupedPeriods)
{
    var cellViewModel = new CellViewModel();
    CellViewModels.Add(cellViewModel);
    foreach (var period in groupedPeriod)
    {
        var rowViewModel = Mapper.Map<RowViewModel>(period); 
        cellViewModel.Rows.Add(rowViewModel);    
    }
}
...

第二个选项可能是更简单的选项,因为映射将更简单。我认为第一个选项可能更难以维护/调试,因为太多的"魔法"会被融入到CreateMap中。

我手边没有任何旧的自动映射器代码,所以我不能给出关于如何配置实际的CreateMap部分的准确代码示例,但是第二个选项应该相当简单。

而且,因为automapper知道如何映射事物列表,你只需告诉它如何映射单个项目,你就可以这样做:

...
foreach (var groupedPeriod in groupedPeriods)
{
    var cellViewModel = new CellViewModel();
    CellViewModels.Add(cellViewModel);
    cellViewModel.Rows = Mapper.Map<List<RowViewModel>>(groupedPeriod);
}
...

这取决于集合Rows的确切类型,如果它不是List<RowViewModel>,则更改该部分,以便AutoMapper创建正确的集合类型。有关映射列表的更多信息请参见:https://github.com/AutoMapper/AutoMapper/wiki/Lists-and-arrays