27

Hi I have some major problems with auto mapper and it being slow. I am not sure how to speed it up.

I am using nhibernate,fluent nhibernate and asp.net mvc 3.0

[Serializable()]
    public class Test
    {
        public virtual int Id { get; private set; }
        public virtual string Name { get;  set; }
        public virtual string Description { get; set; }
        public virtual DateTimeDate { get; set; }
        public virtual IList<Reminder> Reminders { get; set; }
        public virtual IList<Reminder2> Reminders2 { get; set; }
        public virtual Test2 Test2 { get; set; }

        public Test()
        {
            Reminders = new List<Reminders>();
            Reminders2 = new List<Reminders2>();
        }

    }

So as you can see I got some properties, Some other classes as in my database I have references between them.

I then do this

var a = // get all items (returns a collection of Test2)
var List<MyViewModel> collection = new List<MyViewModel>();
     foreach (Test2 t in a)
            {
                MyViewModel vm = Mapper.Map<Test2, MyViewModel>(t);
                vm.SetDateFormat(t.DateTimeDate, DateFilters.All.ToString());

                collection.Add(vm);
            }

// view model

    public class MyViewModel
        {
            public int Id  { get; private set; }
            public string Name { get; set; }
            public string Description { get; set; }
            public DateTime DateTimeDate { get; set; }
            public string FormatedDueDate { get; set; }
            public string Test2Prefix { get; set; }
            public string Test2BackgroundColor { get; set; }
            public string SelectedDateFilter { get; set; }
            public bool DescState { get; set; }
            public bool AlertState { get; set; }


            /// <summary>
            /// Constructor
            /// </summary>
            public MyViewModel()
            {
                // Default values
                SelectedDateFilter = "All";
                DescState = false;
                AlertState = false;
            }

            /// <summary>
            /// Sets the date formatter string used
            /// </summary>
            /// <param name="dateFormat"></param>
            public void SetDateFormat(DateTime dueDate, string dateFilter)
            {
                // simple if statement to format date.
            }
        }

// mapping

  Mapper.CreateMap<Test2,MyViewModel>().ForMember(dest => dest.DescState, opt =>
 opt.ResolveUsing<DescStateResolver>())
                 .ForMember(dest => dest.AlertState, opt =>
 opt.ResolveUsing<AlertStateResolver>());

// resolvers

public class AlertStateResolver : ValueResolver<Task, bool>
    {
        protected override bool ResolveCore(Task source)
        {
            if (source.Reminders.Count > 0 || source.Reminders2.Count > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }   

  public class DescStateResolver : ValueResolver<Task,bool>
    {
        protected override bool ResolveCore(Task source)
        {
            if (String.IsNullOrEmpty(source.Description))
            {
                return false;
            }
            else
            {
                return true;
            }
        }
    }

Ignore the weird names and any typos my real object works just fine and makes sense.

So I used the stop watch and did this

Stopwatch a = new Stopwatch()
    foreach (Test2 t in a)
                {
                    a.Start()                     
                    MyViewModel vm = Mapper.Map<Test2, MyViewModel>(t);
                    a.Stop()
                    vm.SetDateFormat(t.DateTimeDate, DateFilters.All.ToString());

                    collection.Add(vm);
                }

var b = a.Elapsed; // comes back with 32 seconds.

I need to optimized this very badly.

chobo2
  • 83,322
  • 195
  • 530
  • 832
  • I don't know much about automapper, but is there a way to configure it so that it will output preconfigured, hardwired mapping classes? Such classes would almost certainly be orders of magnitude faster. – Robert Harvey Mar 06 '11 at 06:36
  • 1
    I would try 2 things here: Execute this with NHProf, and execute it with dotTrace. Once you have some data, that would be able to point me in the right direction. – Jimmy Bogard Mar 06 '11 at 22:24

7 Answers7

30

Instead of:

var a = // get all items (returns a collection of Test2)
List<MyViewModel> collection = new List<MyViewModel>();
foreach (Test2 t in a)
{
    MyViewModel vm = Mapper.Map<Test2, MyViewModel>(t);
    vm.SetDateFormat(t.DateTimeDate, DateFilters.All.ToString());
    collection.Add(vm);
}

Try:

var a = // get all items (returns a collection of Test2)
List<MyViewModel> collection = Mapper.Map<IEnumerable<Test2>, IEnumerable<MyViewModel>>(a);

which is equivalent to the first except the SetDateFormat call which you could do at your mapping definition. It might also be faster.

If you have a mapping defined between Test2 => MyViewModel AutoMapper automatically provides one for IEnumerable<Test2> => IEnumerable<MyViewModel> so that you don't need to loop.

Also you have mentioned NHibernate in your question. Make sure that your source object along with its collections is eagerly loaded from the database before passing it to the mapping layer or you cannot blame AutoMapper for being slow because when it tries to map one of the collections of your source object it hits the database because NHibernate didn't fetch this collection.

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    It would be interesting to know what kind of speed increase you got using this method... – Tr1stan Nov 17 '11 at 12:57
  • 17
    Just ran a test: Mapping 350 objects with 35 properties using the method in the question took `00:01:52.426584` - Mapping the same objects using the solution took `00:00:00.479615` – Tr1stan Nov 17 '11 at 13:47
  • I have a collection with 6893 objects with 23 properties (test environment, production should have much more). With the loop it took `00:02:32.8118534` and with your solution it took `00:02:25.4527961`. It didn't help that much. Any suggestions? – lpfx Jul 26 '16 at 14:46
  • 3
    this is the key right here when using an ORM: **Make sure that your source object along with its collections is eagerly loaded from the database before passing it to the mapping layer** – chongo2002 Dec 28 '17 at 03:06
29

Another thing to look for is mapping code that throws exceptions. AutoMapper will catch these silently, but catching exceptions in this way impacts performance.

So if SomethingThatMightBeNull is often null, then this mapping will perform poorly due to the NullreferenceExceptions :

.ForMember(dest => dest.Blah, c.MapFrom(src=>src.SomethingThatMightBeNull.SomeProperty))

I've found making a change like this will more than half the time the mapping takes:

.ForMember(dest => dest.Blah, c.MapFrom(src=> (src.SomethingThatMightBeNull == null
    ? null : src.SomethingThatMightBeNull.SomeProperty)))

Update: C# 6 syntax

.ForMember(dest => dest.Blah, c.MapFrom(src => (src.SomethingThatMightBeNull?.SomeProperty)))
Gusdor
  • 14,001
  • 2
  • 52
  • 64
AaronLS
  • 37,329
  • 20
  • 143
  • 202
14

Was Able to improve Launch Time when added this

 .ForAllMembers(options => options.Condition(prop => prop.SourceValue != null));

at end of Each

.CreateMap<..,..>()
a.boussema
  • 1,096
  • 11
  • 19
4

If your subcollections are large you might benefit from using "Any()" instead of the "Count > 1". The Any function will only have to iterate once while the Count might need to iterate the entmes collection (depending on the implementation).

TheNameless
  • 363
  • 3
  • 8
1

I fixed the same issue as yours. It also costs me 32s for mapping only one object. So, I use opts.Ignore() to deal with some customized object as below:

            CreateMap<SiteConfiguration, Site>()
                .ForMember(x => x.SubSystems, opts => opts.Ignore())
                .ForMember(x => x.PointInformations, opts => opts.Ignore())
                .ForMember(x => x.Schedules, opts => opts.Ignore())
                .ForMember(x => x.EquipmentDefinitions, opts => opts.Ignore());

After that, it just cost a few milliseconds.

capcom923
  • 638
  • 5
  • 15
1

Not sure if this is causing any issues in your case, but beware of serializing auto-implemented properties.

Each time your code is compiled, the name of each (anonymous) backing field is picked at random by the compiler. So you may see some surprising exceptions if you serialize data with a progran that is compiled at one time and de-serialize it with a different program.

Chris Bednarski
  • 3,364
  • 25
  • 33
0

A good tip is to optimize the configuration of AutoMapper, use Ignore for the properties of the ViewModels, and make the method call to validate the mappings "Mapper.AssertConfigurationIsValid()".

Mapper.Initialize(cfg =>
        {
            cfg.ValidateInlineMaps = true;
            cfg.AllowNullCollections = false;
            cfg.AllowNullDestinationValues = true;                
            cfg.DisableConstructorMapping(); // <= In the case of my project, I do not use builders, I had a performance gain.
            cfg.AddProfile<DomainToViewModelMappingProfile>();
            cfg.AddProfile<ViewModelToDomainMappingProfile>();
        });
Mapper.AssertConfigurationIsValid();
Jean Gatto
  • 21
  • 1
  • 1
  • 4