简体   繁体   中英

In .Net Core 6 Api, how to map only the values that exist in the DTO uisng AutoMapper 11?

while writing an Api wtih .NET Core 6(This is my first Api), I'm having some trouble while writing an Update method. I need to update my Application table below

 public partial class Application
    {
        public Guid Id { get; set; }
        public DateTime Createddate { get; set; }
        public DateTime Modifieddate { get; set; }
        public string? Companyid { get; set; }
        public Guid? Customerid { get; set; }
        public Guid? Productid { get; set; }
        public string Status { get; set; } = null!;
    }

But only the Status should change and rest must remain same. For this purpose, I wrote a DTO like below,

public class ApplicationUpdateDto
{
    public Guid Id { get; set; }
    public string Status { get; set; }
    public DateTime ModifiedDate = DateTime.Now;
}

And my HttpPut Method is

[HttpPut]
public async Task<IActionResult> Update(ApplicationUpdateDto applicationUpdateDto)
{
    await _service.UpdateAsync(_mapper.Map<Application>(applicationUpdateDto));

    return CreateActionResult(CustomResponseDto<List<NoContentDto>>.Success(204));

}

In this case, request has only 2 data which are ID and Status and when executed,Status changes but also, other values that dont exist in DTO but exists in Application get default values or null values. I'm using Automapper 11 and this is my MapProfile;

public MapProfile()
{
    CreateMap<ApplicationUpdateDto, Application>();
}

How can i solve this and what is the best practise while handling this kind of situations?

I appreciate any help. Thank you.

Firstly you should get data and then update it, also your request Id may not exists you should check it too.

Now ModifiedDate property not neded in your request model

public class ApplicationUpdateDto
{
    public Guid Id { get; set; }
    public string Status { get; set; }
}

[HttpPut]
public async Task<IActionResult> Update(ApplicationUpdateDto model)
{
    var applicationEntity = await _service.GetByIdAsync(model.Id);
    if (applicationEntity == null)
        return BadRequest(400);

    applicationEntity.Status = model.Status;
    applicationEntity.ModifiedDate = DateTime.Now;

    await _service.UpdateAsync(applicationEntity);

    return CreateActionResult(CustomResponseDto<List<NoContentDto>>.Success(204));
}

The IMapperBase interface (which the IMapper interface extends from), has the overload method Map<TSource, TDestination>(TSource source, TDestination destination) defined:

    /// <summary>
    /// Execute a mapping from the source object to the existing destination object.
    /// </summary>
    /// <typeparam name="TSource">Source type to use</typeparam>
    /// <typeparam name="TDestination">Destination type</typeparam>
    /// <param name="source">Source object to map from</param>
    /// <param name="destination">Destination object to map into</param>
    /// <returns>The mapped destination object, same instance as the <paramref name="destination"/> object</returns>
    TDestination Map<TSource, TDestination>(TSource source, TDestination destination);

It allows you you specify the destination object which should be updated instead of creating a new TDestination instance. You can use it to first load your existing Application object and then use it as the destination for your Map(source, destination) call. See the following example:

var config = new MapperConfiguration(cfg => cfg.CreateMap<ApplicationUpdateDto, Application>());
IMapper mapper = config.CreateMapper();

Guid id = Guid.NewGuid();
ApplicationUpdateDto update = new ApplicationUpdateDto
{
    Id = id,
    Status = "completed"
};
Application existing = new Application
{
    Id = id,
    Status = "loading",
    Companyid = "some company id",
    Customerid = Guid.NewGuid(),
    Createddate = DateTime.Today.AddDays(-10),
    Modifieddate = DateTime.Now.AddSeconds(-100000),
    Productid = Guid.NewGuid()
};
Console.WriteLine("Before mapping:");
Console.WriteLine($"Existing Id: {existing.Id}");
Console.WriteLine($"Existing Companyid: {existing.Companyid}");
Console.WriteLine($"Existing Createddate: {existing.Createddate}");
Console.WriteLine($"Existing Modifieddate: {existing.Modifieddate}");
Console.WriteLine($"Existing Customerid: {existing.Customerid}");
Console.WriteLine($"Existing status: {existing.Status}");
        
mapper.Map(update, existing);

Console.WriteLine("After mapping:");
Console.WriteLine($"Existing Id: {existing.Id}");
Console.WriteLine($"Existing Companyid: {existing.Companyid}");
Console.WriteLine($"Existing Createddate: {existing.Createddate}");
Console.WriteLine($"Existing Modifieddate: {existing.Modifieddate}");
Console.WriteLine($"Existing Customerid: {existing.Customerid}");
Console.WriteLine($"Existing status: {existing.Status}");

This will generate the following output before the mapping:

Before mapping:
Existing Id: a242a615-7e88-404d-b364-1b8a8f6e16ab
Existing Companyid: some company id
Existing Createddate: 8/11/2022 12:00:00 AM
Existing Modifieddate: 8/20/2022 6:52:24 AM
Existing Customerid: cdcf2a2b-528d-40f3-9d25-5b33d5255304
Existing status: loading

And after the mapping:

After mapping:
Existing Id: a242a615-7e88-404d-b364-1b8a8f6e16ab
Existing Companyid: some company id
Existing Createddate: 8/11/2022 12:00:00 AM
Existing Modifieddate: 8/21/2022 10:39:04 AM
Existing Customerid: cdcf2a2b-528d-40f3-9d25-5b33d5255304
Existing status: completed

The Status and ModifiedDate properties has been changed, but the other properties stay the same.

Thank you both, both are working very well. At the end I combined these two solutions and come up a solution like below;

[HttpPut]
public async Task<IActionResult> Update(ApplicationUpdateDto applicationUpdateDto)
{
    var application = await _service.GetByIdAsync(applicationUpdateDto.Id);
    if (application == null)
        return BadRequest(400);

    await _service.UpdateAsync(_mapper.Map<ApplicationUpdateDto, Application>(applicationUpdateDto,application));

    return CreateActionResult(CustomResponseDto<List<NoContentDto>>.Success(204));

}

You can set mapping from ApplicationUpdateDto to Application to skip copying Id value.

CreateMap<ApplicationUpdateDto, Application>()
    .ForMember(dest => dest.Id, opt => opt.Ignore());

Afterwards you can use Map(source, destination) overload without need to manually modify Application or ApplicationUpdateDto :

[HttpPut]
public async Task<IActionResult> Update(ApplicationUpdateDto model)
{
    var applicationEntity = await _service.GetByIdAsync(model.Id);
    mapper.Map(applicationUpdateDto, applicationEntity); // this line
    await _service.UpdateAsync(applicationEntity);

    return CreateActionResult(CustomResponseDto<List<NoContentDto>>.Success(204));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM