Search code examples
c#viewmodelautomapperdto

Should AutoMapper also be used as a Re-Shape mapping tool


I know AutoMapper is just to map properties between 2 objects.

Automapper is not for reshaping data this must be programmed in my opinion.

I have a PeriodDTO.IEnumerable<Period> which needs to be reshaped into a PeriodListViewModel.List<CellViewModel>.

Each CellViewModel holds a List<RowViewModel>

This is no 1 to 1 mapping not and I guess maybe this should not be mapped - because its not possible -

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; }
...
}

At the moment the PeriodListViewModel can be easily unit tested. I am asking myself now how can AutoMapper make the situation even better?


Solution

  • While you don't have a trivial 1:1 Mapping from IEnumerable<Period> to List<CellViewModel>, you can still derive some value from Automapper by using it only at the place where you get class-to-class contact, which in your example is between Period and RowViewModel.

    With this in mind, you can also make better use of LINQ and get everything done in one sweeping motion, provided you set up the projections that require re-shaping from Period to RowViewModel.

    Here's a console code example that demonstrates exactly that. Note that we're using Automapper's notion of resolver classes via ValueResolve<TSource,TDestination> do the re-shaping, and I've added an extra constructor to CellViewModel to accept an IEnumerable<RowViewModel>, which helps improve our declarations.

    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();
            }
        }
    }