0

I'm using EF Core and Auto Mapper in an ASP.NET CORE api and I am having a little bit of trouble understanding why I am not getting all my data when I get it by ID.

When I debug and look at the data it is all there in the variable but when it hits the automapper part it is like some data is ignored.

How can I get all the data with the corresponding Id.

this is my method for the GetAsset by Id.

[HttpGet("{key:int}", Name = "getAsset")]
        [EnableQuery]
        public async Task<ActionResult<AssetDto>> GetAsset(int key)
        {
            var asset = await _context.Assets.Include(c => c.Category)
                .Include(m => m.Manufacturer)
                .Include(c => c.Condition)
                .Include(l => l.Location)
                .Include(f => f.AssetFiles)
                .FirstOrDefaultAsync(i => i.Id == key);
            
            if (asset == null) return NotFound();

            var assetDto = _mapper.Map<AssetDto>(asset);
            return assetDto;
        }

Here is my mapping with automapper

CreateMap<Asset, AssetDto>().ReverseMap();

here is my AssetDTO


public class AssetDto
    {   //  Get and sets the Id property
        public int Id { get; set; }
        // Gets and sets the Name Property and sets it to required
        [Required]
        public string Name { get; set; }
        // Gets and sets the CategoryId property
        public int CategoryId { get; set; }
        // Gets and sets the CategoryName property set it to NotMapped
        [NotMapped]
        public string CategoryName { get; set; }
        // Gets and sets the ManufacturerId property
        public int ManufacturerId { get; set; }
        // Gets and sets the ManufacturerName property set it to NotMapped
        [NotMapped]
        public string ManufacturerName { get; set; }
        //Gets and sets the Model property
        public string Model { get; set; }
        //Gets and sets the SerialNumber property
        public string SerialNumber { get; set; }
        // Gets and sets the PurchasePlace property
        public string PurchasePlace { get; set; }
        // Gets and sets the Quantity property
        public int Quantity { get; set; }
        // Gets and sets the AcquiredDate property
        public DateTime AcquiredDate { get; set; }
        // Gets and sets the PurchasePrice property/
        public float PurchasePrice { get; set; }
        // Gets and sets the CurrentValue property
        public float CurrentValue { get; set; }
        //Gets and sets the ConditionId property
        public int ConditionId { get; set; }
        // Gets and sets the ConditionName property set it to NotMapped/
        [NotMapped]
        public string ConditionName { get; set; }
        // Gets and sets the LocationId property
        public int LocationId { get; set; }
        // Gets and sets the LocationName property set it to NotMapped
        [NotMapped]
        public string LocationName { get; set; }
        // Gets and sets the RetiredDate property
        public DateTime RetiredDate { get; set; }
        // Gets and sets the Description property
        public string Description { get; set; }
        // Gets and sets the Files property set it to NotMapped
        [NotMapped]
        public ICollection<AssetFileDto> Files { get; set; } <-- updated
    }

and my Asset Model

public class Asset
    {   // Int property that gets/sets id
        public int Id { get; set; }
        // String property that gets/sets Name
        [Required]
        public string Name { get; set; }
        //Foreign Key
        //Int property that gets/sets category id
        public int? CategoryId { get; set; }
        // Foreign Key
        // Int property that gets/sets Manufacturer id
        public int? ManufacturerId { get; set; }
        // String property that gets/sets model
        public string Model { get; set; }
        // String property that gets/sets serial number
        public string SerialNumber { get; set; }
        // String property that gets/sets purchase place
        public string PurchasePlace { get; set; }
        //Int property that gets/sets quantity
        public int? Quantity { get; set; }
        // Datetime property that gets/sets acquired date
        public DateTime AcquiredDate { get; set; }
        // Float property that gets/sets purchase price
        public float? PurchasePrice { get; set; } 
        //Float property that gets/sets current value
        public float? CurrentValue { get; set; }
        // Foreign Key
        // Int property that gets/sets condition id
        public int? ConditionId { get; set; }
        // Foreign Key
        // Int property that gets/sets location id
        public int? LocationId { get; set; }
        // Datetime property that gets/sets retired date
        public DateTime RetiredDate { get; set; }
        //String property that gets/sets description
        public string Description { get; set; }
        // Foreign Key
        // Collection of asset files
        public ICollection<AssetFile> AssetFiles { get; set; }
        // Navigation Property
        public Category Category { get; set; }
        // Navigation Property
        public Manufacturer Manufacturer { get; set; }
        // Navigation Property
        public Condition Condition { get; set; }
        //Navigation Property
        public Location Location { get; set; }
    } 

The data that I am missing is the CategoryName, ManufacturerName, ConditionName LocationName, And All the asset files that are connected to the asset

When I do a normal get request in postman I get all the data no problem. But when getting it by ID the above fields are missing and I don't know why

any guidance would be grateful

Update:

assetFile DTO

public class AssetFileDto
    {
        // Int property that gets/sets the id
        public int Id { get; set; }
        // Byte property that gets/sets the files
        public byte[] Files { get; set; }
        // String property that gets/sets the name
        public string Name { get; set; }
        // String property that gets/sets the mime type
        public string MimeType { get; set; }
    }

assetFile.cs

public class AssetFile
    {
        // Int property that gets/sets the id
        public int? Id { get; set; }
        // Byte property that gets/sets the File
        public byte[] File { get; set; }
        // String property that gets/sets Name
        public string Name { get; set; }
        // String property that gets/sets the mime type
        public string MimeType { get; set; }
        //Int property that gets/sets the asset id 
        public int AssetId { get; set; }
        // Asset navigation property
        public Asset Asset { get; set; }
    }
DRW
  • 335
  • 1
  • 3
  • 17

1 Answers1

0

You need to expand on your mapping profile. AutoMapper can't work everything out for you. It only maps the properties with the same names automatically.

For example, if you want CategoryName

CreateMap<Asset, AssetDto>()
  .ForMember(x => x.CategoryName, x => x.MapFrom(y => y.Category.Name))
  .ReverseMap();

It's not directly related to this question, but I would recommend that you use ProjectTo so you only select the fields you need, instead of loading them into memory and then filtering.


I checked to see if it worked by added your entities to a local project. Some of the stuff I changed was for part of the demo, so you can just notice the changes and then apply the relevant ones to your code.

Controller / Service

Inject IMapper and use ProjectTo

(you'll need AutoMapper.Extensions.Microsoft.DependencyInjection, if you don't have it already).

private readonly IMapper Mapper;

/* Inject into controller constructor */

public async Task<IActionResult> GetAsset(int key)
{
    var asset = await Db.Assets
            .Where(i => i.Id == key)
            .ProjectTo<AssetDto>(Mapper.ConfigurationProvider)
            .FirstOrDefaultAsync();

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

    return Json(asset);
}

AssetFileDto

If you want it to match with your AssetFile, Files should be called File.

 public byte[] File { get; set; }

Mapping Profile

You should then have a mapping profile that looks something like this

 CreateMap<Asset, AssetDto>()
     .ForMember(x => x.Files, x => x.MapFrom(y => y.AssetFiles));

 CreateMap<AssetFile, AssetFileDto>();
Shoejep
  • 4,414
  • 4
  • 22
  • 26
  • When I try to map the asset files like this ```.ForMember(f => f.Files, f = f.MapFrom(k => k.AssetFiles)).ReverseMap();``` I get the following error in postman ```Self referencing loop detected with type 'home_inventory.Models.AssetFile'. Path 'files[0].asset.assetFiles'.``` how would i get around this in the mapping of the files ? – DRW Nov 18 '20 at 17:49
  • You should have another DTO for `AssetFile` and then put that in your `AssetDTO` and create a mapping for it. e.g. `public ICollection Files { get; set; }` and `CreateMap();` – Shoejep Nov 18 '20 at 17:55
  • I implemented what you recommended but still getting the self referencing loop error in postman – DRW Nov 18 '20 at 18:14
  • You should be able to sort it by looking at the entity but it's difficult to say without seeing it. You can always just disable reference loop handling in the `Startup.cs` by following an answer like [this](https://stackoverflow.com/a/58475098/3688864). – Shoejep Nov 18 '20 at 18:42
  • Tried disabling it and did not go through. Just weird why the mapping is not working how would you do the ForMember. did it look like i had it right i tried it also on the mapping for the assetfiles => assetfilesdto and still did notwork. Will keep trying to find a solution. – DRW Nov 18 '20 at 18:53
  • Can you edit your question with what your `AssetFile` and `AssetFileDTO` looks like? Can you also explain what "did not go through" means? – Shoejep Nov 18 '20 at 18:57
  • Edited the original post and did not go though was the attempt at disabling the Json reference loop./ – DRW Nov 18 '20 at 19:05
  • It seemed to work when I checked it. I've edited my answer with what I did. Hopefully it will work for you. – Shoejep Nov 18 '20 at 19:34
  • Thanks for the help – DRW Nov 18 '20 at 19:41