英文:
ASP.NET Core - DTO not working as expected with auto-mapper and Dapper implementation
问题
在我的ASP.NET Core 6 Web API应用程序中,我正在使用Dapper与DTO一起返回查询数据库的结果。
Response类:
public class Response<T>
{
    public T Data { get; set; }
    public bool Successful { get; set; }
    public string Message { get; set; }
    public int StatusCode { get; set; }
    public Response(int statusCode, bool success, string msg, T data, List<string> errors)
    {
        Data = data;
        Successful = success;
        StatusCode = statusCode;
        Message = msg;
    }
    public Response()
    {
    }
    public static Response<T> Success(string successMessage, T data, int statusCode = 200)
    {
        return new Response<T> { Successful = true, Message = successMessage, Data = data, StatusCode = statusCode };
    }
    public override string ToString() => JsonConvert.SerializeObject(this);
}
我有这个模型类:
[Table("employees")]
public class Employee
{
    public string EMPLOYEE_CODE { get; set; }
    public string EMPLOYEE_NAME { get; set; }
    public string EMPLOYEE_ADDR1 { get; set; }
    public string EMPLOYEE_ADDR2 { get; set; }
}
然后我创建了这个DTO:
public class EmployeeDto
{
    public string EmployeeCode { get; set; }
    public string EmployeeName { get; set; }
    public string EmployeeAddr1 { get; set; }
    public string EmployeeAddr2 { get; set; }
}
这是映射配置:
public class EmployeeMapperProfile : Profile
{
    public EmployeeMapperProfile() 
    {
        CreateMap<Employee, EmployeeDto>()
            .ForMember(
                dest => dest.EmployeeCode,
                opt => opt.MapFrom(src => src.EMPLOYEE_CODE)
            )
            .ForMember(
                dest => dest.EmployeeName,
                opt => opt.MapFrom(src => src.EMPLOYEE_NAME)
            )
            .ForMember(
                dest => dest.EmployeeAddr1,
                opt => opt.MapFrom(src => src.EMPLOYEE_ADDR1)
            )
            .ForMember(
                dest => dest.EmployeeAddr2,
                opt => opt.MapFrom(src => src.EMPLOYEE_ADDR2)
            )
            .ReverseMap();
    }
}
我使用AutoMapper进行了映射,如下所示:
public static class AutoMapperServiceExtension
{
    public static void ConfigureAutoMappers(this IServiceCollection services)
    {
        services.AddAutoMapper(typeof(EmployeeMapperProfile));
    }
}
在Program.cs中注入它如下:
builder.Services.ConfigureAutoMappers();
MemoryCache:
public class CacheService : ICacheService
{
    private readonly IMemoryCache _memoryCache;
    public CacheService(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }
    public T GetData<T>(string key)
    {
        try
        {
            T item = (T)_memoryCache.Get(key);
            return item;
        }
        catch (Exception e)
        {
            throw;
        }
    }
}
然后我有存储库代码:
public async Task<Response<IEnumerable<EmployeeDto>>> GetAllEmployeesAsync()
{
    var response = new Response<IEnumerable<EmployeeDto>>();
    using IDbConnection dbConnection = Connection;
    try
    {
        string sQuery = "SELECT * FROM employees";
        dbConnection.Open();
        var cacheData = _cacheService.GetData<IEnumerable<EmployeeDto>>(sQuery);
        if (cacheData != null)
        {
            response.Data = cacheData;
            return response;
        }
        var expirationTime = DateTimeOffset.Now.AddHours(24.0);
        cacheData = await dbConnection.QueryAsync<EmployeeDto>(sQuery);
        response.Data = cacheData;
        response.StatusCode = 200;
        response.Successful = true;
        return response;
    }
    catch (Exception ex)
    {
        response.Message = "Error occured: " + ex.Message;
        response.Successful = false;
        response.StatusCode = (int)HttpStatusCode.BadRequest;
        return response;
    }
    finally
    {
        dbConnection.Close();
    }
}
最后是控制器:
[Route("api/[controller]")]
[ApiController]
public class EmployeesController : ControllerBase
{
    private readonly IEmployeeRepository _employeeRepository;
    public EmployeesController(IEmployeesRepository employeeRepository)
    {
        _employeeRepository = employeeRepository;
    }
    [HttpGet("GetAllEmployees")]
    public async Task<ActionResult<Response<EmployeeDto>>> GetQueryEmployeeAsync()
    {
        var result = await _employeeRepository.GetAllEmployeesAsync();
        return Ok(result);
    }
}
然而,当我在Swagger中启动它时,不是每个字段都填充了数据,而是为null,如下所示。
Swagger响应:
{
  "data": [
    {
      "employeeCode": null,
      "employeeName": null,
      "employeeAddr1": null,
      "employeeAddr2": null
    },
    {
      "employeeCode": null,
      "employeeName": null,
      "employeeAddr1": null,
      "employeeAddr2": null
    }
  ],
  "successful": true,
  "statusCode": 0
}
但如果我删除DTO,并使用普通模型,它会正确填充数据。
如何纠正这个问题?
英文:
In my ASP.NET Core 6 Web API application, I am using Dapper in connection with DTO to return results of queried database.
Response class:
public class Response<T>
{
    public T Data { get; set; }
    public bool Successful { get; set; }
    public string Message { get; set; }
    public int StatusCode { get; set; }
    public Response(int statusCode, bool success, string msg, T data, List<string> errors)
    {
        Data = data;
        Successful = success;
        StatusCode = statusCode;
        Message = msg;
    }
    public Response()
    {
    }
    public static Response<T> Success(string successMessage, T data, int statusCode = 200)
    {
        return new Response<T> { Successful = true, Message = successMessage, Data = data, StatusCode = statusCode };
    }
    public override string ToString() => JsonConvert.SerializeObject(this);
}
I have this model class:
[Table("employees")]
public class Employee
{
    public string EMPLOYEE_CODE { get; set; }
    public string EMPLOYEE_NAME { get; set; }
    public string EMPLOYEE_ADDR1 { get; set; }
    public string EMPLOYEE_ADDR2 { get; set; }
}
Then I created this DTO:
public class EmployeeDto
{
    public string EmployeeCode { get; set; }
    public string EmployeeName { get; set; }
    public string EmployeeAddr1 { get; set; }
    public string EmployeeAddr2 { get; set; }
}
Here's the mapping:
public class EmployeeMapperProfile : Profile
{
    public EmployeeMapperProfile() 
    {
        CreateMap<Employee, EmployeeDto>()
            .ForMember(
                dest => dest.EmployeeCode,
                opt => opt.MapFrom(src => src.EMPLOYEE_CODE)
            )
            .ForMember(
                dest => dest.EmployeeName,
                opt => opt.MapFrom(src => srcEMPLOYEE_NAME)
            )
            .ForMember(
                dest => dest.EmployeeAddr1,
                opt => opt.MapFrom(src => src.EMPLOYEE_ADDR1)
            )
            .ForMember(
                dest => dest.EmployeeAddr2,
                opt => opt.MapFrom(src => src.EMPLOYEE_ADDR2)
            )
            .ReverseMap();
    }
}
I did the mapping using AutoMapper as shown here:
public static class AutoMapperServiceExtension
{
    public static void ConfigureAutoMappers(this IServiceCollection services)
    {
        services.AddAutoMapper(typeof(EmployeeProfile));
    }
}
Injected it in Program.cs as shown below:
builder.Services.ConfigureAutoMappers();
MemoryCache:
public class CacheService : ICacheService
{
    private readonly IMemoryCache _memoryCache;
    public CacheService(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }
    public T GetData<T>(string key)
    {
        try
        {
            T item = (T)_memoryCache.Get(key);
            return item;
        }
        catch (Exception e)
        {
            throw;
        }
    }
}
Then I have the repository code
public async Task<Response<IEnumerable<EmployeeDto>>> GetAllEmployeesAsync()
{
    var response = new Response<IEnumerable<EmployeeDto>>();
    using IDbConnection dbConnection = Connection;
    try
    {
        string sQuery = @"SELECT * FROM employees";
        dbConnection.Open();
        var cacheData = _cacheService.GetData<IEnumerable<EmployeeDto>>(sQuery);
        if (cacheData != null)
        {
            response.Data = cacheData;
            return response;
        }
        var expirationTime = DateTimeOffset.Now.AddHours(24.0);
        cacheData = await dbConnection.QueryAsync<EmployeeDto>(sQuery);
        response.Data = cacheData;
        response.StatusCode = 00;
        response.Successful = true;
        return response;
    }
    catch (Exception ex)
    {
        response.Message = $"Error occured: " + ex.Message;
        response.Successful = false;
        response.StatusCode = (int)HttpStatusCode.BadRequest;
        return response;
    }
    finally
    {
        dbConnection.Close();
    }
}
And finally the controller:
[Route("api/[controller]")]
[ApiController]
public class EmployeesController : ControllerBase
{
    private readonly IEmployeeRepository _employeeRepository;
    public EmployeesController(IEmployeesRepository EmployeeRepository)
    {
        _employeeRepository = employeeRepository;
    }
    [HttpGet("GetAllEmployees")]
    public async Task<ActionResult<Response<EmployeeDto>>> GetQueryEmployeeAsync()
    {
        var result = await _employeeRepository.GetAllEmployeesAsync();
        return Ok(result);
    }
}
However when I launched it in Swagger, instead of getting each field populated with data, they are null as shown below.
Swagger response:
{
  "data": [
    {
      "employeeCode": null,
      "employeeName": null,
      "employeeAddr1": null,
      "employeeAddr2": null
    },
    {
      "employeeCode": null,
      "employeeName": null,
      "employeeAddr1": null,
      "employeeAddr2": null
    }
  ],
  "successful": true,
  "statusCode": 0
}
But if I remove the DTO, and use the normal model, it populates the data just fine.
How do I correct this?
答案1
得分: 1
这只是一个示例。您也可以将IMapper注入到您的CacheService中。
重要的是,您必须调用_mapper.Map。
英文:
Assuming this is your repository class
public class DataRepo 
{
    private readonly IMapper _mapper;
    // Inject Automapper through your constructor
    public DataRepo(IMapper mapper)
    {
         _mapper = mapper;
    }
    public async Task<Response<IEnumerable<EmployeeDto>>> GetAllEmployeesAsync()
    {
        // removed for brevity
        try
        {
           // removed for brevity
           // changed IEnumerable<EmployeeDto> to IEnumerable<Employee>
           var cacheData = _cacheService.GetData<IEnumerable<Employee>>(sQuery);
           if (cacheData != null)
           {
                // this is how you call the automapper
                response.Data = _mapper.Map<IEnumerable<EmployeeDto>>(cacheData);
                return response;
           }
           //UPDATED LINES
           var cacheData2 = await dbConnection.QueryAsync<EmployeeDTO>(sQuery);
           response.Data = cacheData2;
           // removed for brevity
           return response;
         }
         catch (Exception ex)
         {
             //your code
         }
        finally
        {
            //your code
        }
    }
}
This is just an example. You can also inject the IMapper into your CacheService.
What matters is, you have to call _mapper.Map
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论