简体   繁体   中英

How to manually mapping DTO WITHOUT using AutoMapper?

I'm learning C#.NET Core and trying to create DTO mapping without using AutoMapper as I'm working on a small project alone and want to understand fundamental before using extra packages, surpringly I could not easily find answer at stackoverflow.com or I may use wrong keyword searching.

BTW, below is my code which I successfully map to EmployeeForShortDto under GetEmployee method. Unfortunately, I don't how to map it under GetAllEmployee just because the return data is a collection, not a single record. Please advice.

EmployeeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NetCoreWebApplication1.Dto;
using NetCoreWebApplication1.Repository;
using NetCoreWebApplication1.Other;

namespace NetCoreWebApplication1.Controller
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        private readonly IMasterRepository _repo;

        public EmployeeController(IMasterRepository repo)
        {
            _repo = repo;
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> GetEmployee(int id)
        {
            var data = await _repo.GetEmployee(id);
            if (data == null) return NotFound();
            var dataDto = new EmployeeForShortDto()
            {
                Id = data.Id,
                EmpCode = data.EmpCode,
                Fname = data.Fname,
                Lname = data.Lname,
                Age = NetCoreWebApplication1.Other.Extension.CalcAge(data.DateBirth)
            };

            return Ok(dataDto);
        }

        [HttpGet]
        public async Task<IActionResult> GetAllEmployee()
        {
            var data = await _repo.GetAllEmployee();
            return Ok(data);
        }

    }
}

MasterRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NetCoreWebApplication1.Models;

namespace NetCoreWebApplication1.Repository
{
    public class MasterRepository : IMasterRepository
    {
        private readonly PrDbContext _context;

        public MasterRepository(PrDbContext context)
        {
            _context = context;
        }


        // Employee
        public async Task<List<Employee>> GetAllEmployee()
        {
            var data = await _context.Employee.ToListAsync();
            return data;
        }

        public async Task<Employee> GetEmployee(int id)
        {
            var data = await _context.Employee.FirstOrDefaultAsync(x => x.Id == id);
            return data;
        }

        // Generic methods
        public void Add<T>(T entity) where T : class
        {
            _context.Add(entity);
        }

        public void Delete<T>(T entity) where T : class
        {
            _context.Remove(entity);
        }

        public async Task<bool> SaveAll()
        {
            return await _context.SaveChangesAsync() > 0;
        }
    }
}

You can use an extension method to map from your entity type to your DTO type.

public static EmployeeForShortDto ToDto(this Employee employee)
{
    if (employee != null)
    {
        return new EmployeeForShortDto
        {
            Id = employee.Id,
            EmpCode = employee.EmpCode,
            Fname = employee.Fname,
            Lname = employee.Lname,
            Age = NetCoreWebApplication1.Other.Extension.CalcAge(employee.DateBirth)
        };
    }

    return null;
}

And then use where needed.

[HttpGet("{id}")]
public async Task<IActionResult> GetEmployee(int id)
{
    var data = await _repo.GetEmployee(id);

    if (data == null) 
    {
        return NotFound();
    }

    return Ok(data.ToDto());
}

[HttpGet]
public async Task<IActionResult> GetAllEmployee()
{
    var data = await _repo.GetAllEmployee();

    return Ok(data.Select(x => x.ToDto()));
}

Okay, the direct answer to your question is "do it pr returned values";

List<EmployeeForShortDto> result = new List<EmployeeForShortDto>();
foreach(Employee dbEmployee in data )
{
 result.add(new EmployeeForShortDto()
            {
                Id = dbEmployee .Id,
                EmpCode = dbEmployee .EmpCode,
                Fname = dbEmployee .Fname,
                Lname = dbEmployee .Lname,
                Age = NetCoreWebApplication1.Other.Extension.CalcAge(dbEmployee .DateBirth)
            });
}

This however, is type specific for your item. Why not make a generic method that uses reflection to map the object either by attributes attached, or by property name directly? If you get it done, you will be able to transfer any object to a DTO, as long as you adhere to the internal rules of property names or setup up the mappings via the attributes.

For your problem, extract your implementation in a new method.

EmployeeForShortDto ConvertToDto(Employee data)
{
 var dataDto = new EmployeeForShortDto()
        {
            Id = data.Id,
            EmpCode = data.EmpCode,
            Fname = data.Fname,
            Lname = data.Lname,
            Age = NetCoreWebApplication1.Other.Extension.CalcAge(data.DateBirth)
        };
}

Then finally call it in loop,

 foreach(Employee e in EmployeeList)
    { 
       dtoList.Add(ConvertToDto(e));
    }

For generic implementation, Generate a list of properties of Model and Dto via reflection. and then match their types.

class AdapterHelper<T1, T2>
{
    public T1 Adapt(T2 source)
    {
        T1 targetItem = Activator.CreateInstance<T1>();
        var props = typeof(T1).GetProperties();
        var targetProps = typeof(T2).GetProperties();
        foreach (var prop in props)
        {
            foreach (var targetProp in targetProps)
            {
                if (prop.Name == targetProp.Name)
                {
                    targetProp.SetValue(targetItem, prop.GetValue(source));
                    //assign

                }
            }
        }
        return targetItem;
    }
}

This is the link to my original answer.

thank you for all responses, all are very useful to me. Finally, I end up with solution from @Brad. I also learned how to make a reverse mapping from DTO to a class before adding a record to the database.

I put my code below in case someone want to see. Any comments/suggestions are more than welcome. Thank you.

Extension.cs

using NetCoreWebApplication1.Dto;
using NetCoreWebApplication1.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NetCoreWebApplication1.Other
{
    public static class Extension
    {
        public static EmployeeForShortDto MapToEmployeeForShortDto(this Employee emp)
        {
            if (emp != null)
            {
                return new EmployeeForShortDto
                {
                    Id = emp.Id,
                    EmpCode = emp.EmpCode,
                    Fname = emp.Fname,
                    Lname = emp.Lname,
                    Age = emp.DateBirth.CalcAge()
                };
            }

            return null;
        }

        public static EmployeeForListDto MapToEmployeeForListDto(this Employee emp)
        {
            if (emp != null)
            {
                return new EmployeeForListDto
                {
                    Id = emp.Id,
                    EmpCode = emp.EmpCode,
                    Fname = emp.Fname,
                    Lname = emp.Lname,
                    Age = emp.DateBirth.CalcAge(),
                    EntityCode = emp.EntityCode,
                    IsActive = emp.IsActive
                };
            }

            return null;
        }

        public static Employee MapFromEmployeeForAddDto(this EmployeeForAddDto emp)
        {
            if (emp != null)
            {
                return new Employee
                {
                    EmpCode = emp.EmpCode,
                    Fname = emp.Fname,
                    Lname = emp.Lname,
                    IdCard = emp.IdCard,
                    IsActive = 1
                };
            }

            return null;
        }

        public static int CalcAge(this DateTime? dateBirth)
        {
            if (dateBirth.HasValue)
            {
                var age = DateTime.Today.Year - dateBirth.Value.Year;
                if (dateBirth.Value.AddYears(age) > DateTime.Today) age--;
                return age;
            }
            else
            {
                return 0;
            }
        }
    }
}

MasterRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NetCoreWebApplication1.Dto;
using NetCoreWebApplication1.Models;

namespace NetCoreWebApplication1.Repository
{
    public class MasterRepository : IMasterRepository
    {
        private readonly PrDbContext _context;

        public MasterRepository(PrDbContext context)
        {
            _context = context;
        }


        // Employee
        public async Task<List<Employee>> GetAllEmployee()
        {
            var data = await _context.Employee.ToListAsync();
            return data;
        }

        public async Task<Employee> GetEmployee(int id)
        {
            var data = await _context.Employee.FirstOrDefaultAsync(x => x.Id == id);
            return data;
        }

        public async Task<Employee> AddEmployee(Employee data)
        {
            await _context.Employee.AddAsync(data);
            await _context.SaveChangesAsync();
            return data;
        }

        public async Task<bool> EmployeeExists(string entityCode, string empCode)
        {
            if (await _context.Employee.AnyAsync(x =>
                x.EntityCode == entityCode &&
                x.EmpCode == empCode))
                return true;

            return false;
        }

        // Generic methods
        public void Add<T>(T entity) where T : class
        {
            _context.Add(entity);
        }

        public void Delete<T>(T entity) where T : class
        {
            _context.Remove(entity);
        }

        public async Task<bool> SaveAll()
        {
            return await _context.SaveChangesAsync() > 0;
        }
    }
}

EmployeeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NetCoreWebApplication1.Dto;
using NetCoreWebApplication1.Repository;
using NetCoreWebApplication1.Other;
using NetCoreWebApplication1.Models;

namespace NetCoreWebApplication1.Controller
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        private readonly IMasterRepository _repo;

        public EmployeeController(IMasterRepository repo)
        {
            _repo = repo;
        }

        [HttpPost("add")]
        public async Task<IActionResult> AddEmployee(EmployeeForAddDto emp)
        {
            if (await _repo.EmployeeExists(emp.EntityCode, emp.EmpCode))
                ModelState.AddModelError("Employee", "Employee is duplicate (EntityCode + EmpCode)");

            if (!ModelState.IsValid)
                return BadRequest(ModelState);

            Employee employeeToAdd = emp.MapFromEmployeeForAddDto();

            await _repo.AddEmployee(employeeToAdd);

            return StatusCode(201);
        }


        [HttpGet("{id}")]
        public async Task<IActionResult> GetEmployee(int id)
        {
            var data = await _repo.GetEmployee(id);

            if (data == null) return NotFound();

            return Ok(data.MapToEmployeeForShortDto());
        }

        [HttpGet]
        public async Task<IActionResult> GetAllEmployee()
        {
            var data = await _repo.GetAllEmployee();

            //var dataDto = data.Select(x => x.MapToEmployeeForShortDto());
            var dataDto = data.Select(x => x.MapToEmployeeForListDto());

            return Ok(dataDto);
        }

    }
}

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