Automapper: How to leverage a custom INamingConven

2019-01-29 00:50发布

问题:

I am working with a database where the designers really seemed to enjoy capital letters and the underscore key. Since I have a simple ORM, my data models use these names as well. I need to build DTOs and I would prefer to give them standard names since we are exposing them through services. The code below is now corrected! The test passes so use this as a reference if you need to use multiple naming conventions

    using System;
    using System.Collections.Generic;
    using System.Text.RegularExpressions;
    using AutoMapper;
    using NUnit.Framework;

    namespace AutomapperTest
    {
        public class DATAMODEL
        {
            public Guid ID { get; set; }
            public string FIRST_NAME { get; set; }
            public List<CHILD_DATAMODEL> CHILDREN { get; set; }
        }

        public class CHILD_DATAMODEL
        {
            public Guid ID { get; set; }
            public int ORDER_ID { get; set; }
        }

        public class DataModelDto
        {
            public Guid Id { get; set; }
            public string FirstName { get; set; }
            public List<ChildDataModelDto> Children { get; set; }
        }

        public class ChildDataModelDto
        {
            public Guid Id { get; set; }
            public int OrderId { get; set; }
        }

        public class UpperUnderscoreNamingConvention : INamingConvention
        {
            private readonly Regex _splittingExpression = new Regex(@"[\p{Lu}0-9]+(?=_?)");

            public Regex SplittingExpression { get { return _splittingExpression; } }

            public string SeparatorCharacter { get { return "_"; } }
        }

        public class Profile1 : Profile
        {
            protected override void Configure()
            {
                SourceMemberNamingConvention = new UpperUnderscoreNamingConvention();
                DestinationMemberNamingConvention = new PascalCaseNamingConvention();
                CreateMap<DATAMODEL, DataModelDto>();
                CreateMap<CHILD_DATAMODEL, ChildDataModelDto>();
            }
        }
        [TestFixture]
        public class Tests
        {
            [Test]
            public void CanMap()
            {
                //tell automapper to use my convention
                Mapper.Initialize(x => x.AddProfile<Profile1>());
                //make a dummy source object
                var src = new DATAMODEL();
                src.ID = Guid.NewGuid();
                src.FIRST_NAME = "foobar";
                src.CHILDREN = new List<CHILD_DATAMODEL>
                               {
                                   new CHILD_DATAMODEL()
                                       {
                                           ID = Guid.NewGuid(),
                                           ORDER_ID = 999
                                       }
                               };
                //map to destination
                var dest = Mapper.Map<DATAMODEL, DataModelDto>(src);
                Assert.AreEqual(src.ID, dest.Id);
                Assert.AreEqual(src.FIRST_NAME, dest.FirstName);
                Assert.AreEqual(src.CHILDREN.Count, dest.Children.Count);
                Assert.AreEqual(src.CHILDREN[0].ID, dest.Children[0].Id);
                Assert.AreEqual(src.CHILDREN[0].ORDER_ID, dest.Children[0].OrderId);
            }
        }
    }

回答1:

Create your mappings in profiles, and define the INamingConvention parameters as appropriate.

I don't like the global/static, so I prefer using Initialize and define all of my mappings together. This also has the added benefit of allowing a call to AssertConfiguration... which means if I've borked my mapping I'll get the exception at launch instead of whenever my code gets around to using the problematic mapping.

Mapper.Initialize(configuration =>
{
    configuration.CreateProfile("Profile1", CreateProfile1);
    configuration.CreateProfile("Profile2", CreateProfile2);
});
Mapper.AssertConfigurationIsValid();

in the same class with that initialization method:

public void CreateProfile1(IProfileExpression profile)
{
    // this.CreateMap (not Mapper.CreateMap) statements that do the "normal" thing here
    // equivalent to Mapper.CreateMap( ... ).WithProfile("Profile1");
}

public void CreateProfile2(IProfileExpression profile)
{
    profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
    profile.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();

    // this.CreateMap (not Mapper.CreateMap) statements that need your special conventions here
    // equivalent to Mapper.CreateMap( ... ).WithProfile("Profile2");
}

if you do it this way, and don't define the same mapping in both profiles, I don't think you need anything to "fill in the blank" from the original question, it should already be setup to do the right thing.



回答2:

What about

public class DATAMODELProfile : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<DATAMODEL, DATAMODEL>();
        Mapper.CreateMap<DATAMODEL, SOMETHINGELSE>();
        Mapper.CreateMap<DATAMODEL, DataModelDto>()
            .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.ID))
            .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FIRST_NAME))
            .ForMember(dest => dest.ChildDataModels, opt => opt.MapFrom(src => src.CHILD_DATAMODELS));
    }
}