How Automapper globally configures map conditions to filter null values for all mappings

We often encounter the following problems when using automapper: Suppose we use the UserDto class for the data we display to the user, and the User class is our entity class. When editing to the user, we may have fields that are Null in the database and need default values such as BirTime in UserDto here.Then some of us have the habit of assigning values in constructors

  public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public  DateTime? BirTime{ get; set; }
    }

   public class UserDto
    {
         public UserDto()
        {
             this.BirTime=DateTime.Now;//Assign the current time to BirTime
        }
        public string Name { get; set; }
        public  DateTime? BirTime{ get; set; }
    }

 //Suppose we take the value from the database like this, and then we want to convert it to and from UserDto
   User user = new User()
    {
        Id = 1,
        Name = "caoyc",
        BirTime=null
    };

UserDto userDto=user.MapTo<UserDto>();//This converts the entity class into a UserDto class that is presented to the user in the foreground

It looks like there's nothing wrong with the code here, but I want to use the default value in my UserDto class when BirTime is null, which is the current time, but after the actual operation you will find that the converted UserDto value has changed. So the problem is, how can I use the default value?

There are many methods, such as

//First method
UserDto userDto=user.MapTo<UserDto>();//This converts the entity class into a UserDto class that is presented to the user in the foreground
userDto.BirTime=userDto.BirTime==null?DateTime.Now:userDto.BirTime;

The first method is to make a judgement after the conversion is complete and then reassign, in which case there is no need to write a constructor

//Second method
Mapper.CreateMap<User, UserDto>().ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));

The second method is not very good, when you need to convert many classes, each of them should be written like this, many will also be very annoying

//Third, add this sentence when configuring globally
private static void CreateMappingsInternal(IMapperConfigurationExpression cfg)
{
     //That's right
     cfg.ForAllMaps((a, b) => b.ForAllMembers(opt => opt.Condition((src, dest, sourceMember) => sourceMember != null)));
}

Personally, I recommend the third method, but it's like killing with a stick. It has both advantages and disadvantages.

[C#] AutoMapper uses learning notes

What is AutoMapper?

AutoMapper is an open source library that automates object-to-object conversion; the usual conversion between DTO (Data Transfer Object Data Transfer Object Data Transfer Object Data Transfer Object Data Transfer Object) and Model makes code fairly lengthy. To reduce this workload, the AutoMapper author wrote this open source library so that the conversion between DTO and Model can be completed automatically.

During the learning process, I read a lot of materials, and one of the blog posts that helped me a lot is:http://www.codeproject.com/Articles/61629/AutoMapper Full English is still stressful for me, but it's still a bit chewed.

Basic usage:

public class DTO
{
    public string userName {set; get;}
    public string age {set; get;}
    public string job {set; get;}
}
public class Model
{
    public string userName {set; get;}
    public string age {set; get;}
    public string job {set; get;}
}

The DTO and Model mentioned above, in general, are directly mapping the data in the database, and the Model is interactive data. How do you map the data in the two objects?

  • The usual practice is:
//This can be done between DTO and Model because the object's properties are of type string
DTO.userName = Model.userName;
DTO.age = Model.age ;
DTO.job = Model.job ;
  • Using AutoMapper transformation:
//First you need to define a mapping relationship between DTO and Model
Mapper.CreateMap<DTO, Model>();
DTO dtoData = GetdtoDataFromDB();
Model modelData = Mapper.Map<DTO, Model>(dtoData );

The DTO object here is automatically transformed into a Model object by AutoMapper, so the values of userName, age, and job in the modelData are the values taken out by the GetdtoDataFromDB() method.

Points encountered

When using AutoMapper to transform DTO and Model automatically in a real project, I encountered a pit, which is not really a pit, but a place I didn't notice. Make a record here to enhance my impression.
DTO and Model are structured as follows:

// DTO
public class DTO
{ 
    public string userName {set; get;} 
    public string age {set; get;} 
    public string job {set; get;}
    public AddressDTO address {set ; get;}
}
public class AddressDTO{ 
    public string country {set; get;} 
    public string province {set; get;} 
}
// Model
public class Model
{ 
    public string userName {set; get;} 
    public string age {set; get;} 
    public string job {set; get;}
    public AddressModel address {set ; get;}
}
public class AddressModel{ 
    public string country {set; get;} 
    public string province {set; get;} 
}

Having written these two objects, you begin to write as above and start auto-transforming with AutoMapper

Mapper.CreateMap<DTO, Model>();
DTO dtoData = GetdtoDataFromDB();
Model modelData = Mapper.Map<DTO, Model>(dtoData );

When I finished, I was very happy and I was amazed at the power of AutoMapper.

From the error result analysis, the reason is that I have not configured correctly, how to solve this problem?
Simply, include transformations between child objects like this, as long as you also use AutoMapper to define mapping relationships between child objects?

Action
Mapper.CreateMap<DTO, Model>();
Mapper.CreateMap<AddressDTO , AddressModel >();//Subobject mapping relationship
DTO dtoData = GetdtoDataFromDB();
Model modelData = Mapper.Map<DTO, Model>(dtoData );

In this way, the project will run successfully, and one of the benefits of AutoMapper is that if we need a new field, such as a new one in the DTO

public string sex {set; get;}

If it's a regular writing style at this time, we need to add it manually where we're transforming it

DTO.sex = Model.sex;

As the project gets bigger and bigger, we need to be extremely careful about making this change.
Using AutoMapper to achieve automatic transformation, you only need to add new fields to the DTO and Model to avoid the risk of handwritten code.

Last

AutoMapper also has many powerful functions, such as customizing mapping relationships;
If used in future learning, they will also be recorded as notes.

C# AutoMapper Trench

Differences between before and ForMember s and after in AutoMapper

I've prepared a test case here, and we're trying out some methods directly when we convert entities to DToS

CreateMap<TestEntity, TestDto>();

However, it is inevitable that some field type changes will be needed. Here is the difference between before and ForMember s

1. Use of before

Beforeuses the field values used to change entities before they convert dto, and assigns them to dto.

The id here is originally 1, but after assignment by before transform, the output to Du dto becomes 3;

2. Use of after

AfterUse to change the value of the output dto after the entity has converted the dto. After the entity has been assigned a value, the type here will not be wrong if it is OK, but it will be wrong if it is not.

Here's a demonstration

Correct type

Type error: Introduce string to array object

When the string here is converted to an array object, a type error is reported, which is also the pit I encountered. Neither before nor after can be used at this time.

3. Use of ForMember s

Use ForMember s here to convert attribute conditions

Attributes converted successfully

public void Test1()
        {
            List<Detail> details = new List<Detail>
            {
                new Detail
                {
                    Id=1,
                    Name="sing"
                },
                 new Detail
                {
                    Id=1,
                    Name="jump"
                },
                  new Detail
                {
                    Id=1,
                    Name="rap"
                },
                   new Detail
                {
                    Id=1,
                    Name="Basketball"
                },
            };

            TestEntity test = new TestEntity
            {
                Id = 1,
                Name = "Xiao Ming",
                Introduce = JsonConvert.SerializeObject(details)
            };
            var config = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<TestEntity, TestDto>();
                // Represents source type naming rules
                cfg.SourceMemberNamingConvention = new PascalCaseNamingConvention();
                //Represents the target type naming rules
                cfg.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
                //LowerUnderscoreNamingConvention and Pascal CaseNamingConvention are two naming rules provided by AutoMapper. The former is named in lowercase and contains underscores, and the latter is the Pascal naming rule (the first letter of each word is uppercase).
            });
            var result = config.CreateMapper().Map<TestDto>(test);
        }
        public class TestEntity
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string Introduce { get; set; }
        }
        public class TestDto
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public List<Detail> Introduce { get; set; }

        }
        public class Detail
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }

Posted by DepretioN on Thu, 09 Sep 2021 09:46:04 -0700