2

I think I miss something in my code which cause this error : AutoMapperMappingException: Missing type map configuration or unsupported mapping. this is my first time that I try to use autoMapper for filling my dto from datatable. so I found an example from How do I use automapper to map a dataset with multiple tables . so this is my code :

public class ContractController : ApiController
    {
        private readonly IMapper _mapper;

        public ContractController()
        {
            var mapperConfig = new MapperConfiguration(x =>
                x.CreateMap<IDataReader, ContractListDto>());
            _mapper = mapperConfig.CreateMapper();
        }

        [Route("api/Sales/ContractsList")]
        [HttpGet]
        public IHttpActionResult  Get()
        {
            var salesHelper = new SalesHelper(enmSaleDocType.enmSaleDocType_SaleContract,
                enmSaleAfterSaleMode.enmSaleAfterSaleMode_Sale,
                enmSaleOperationType.enmSaleOperationType_Sales);
            var saleDataTable = salesHelper.GetSales();
            IEnumerable<ContractListDto> contractsDto = null;
            using (var saleDataReader = saleDataTable.CreateDataReader())
            {
                 contractsDto = _mapper.Map<IEnumerable<ContractListDto>>(saleDataReader);
            }
            
            
            return Ok(contractsDto);
        }
    }

I am utilizing api2 dotnet framework and automapper version 10. so I cannot initialize automapper in globol.asax. so I try to achieve this from constructor as you see in my code.(may I am doing something wrong there! ). at the end I get error here:

using (var saleDataReader = saleDataTable.CreateDataReader())
            {
                 contractsDto = _mapper.Map<IEnumerable<ContractListDto>>(saleDataReader);
            }

when I am trying to map. thanks for reading.

eshghi ali
  • 35
  • 5
  • Why use AutoMapper at all? what does `GetSales` do? You wouldn't need to map anything if you used eg EF Core or [Dapper](https://github.com/StackExchange/Dapper). You wouldn't need to handle raw data readers either – Panagiotis Kanavos Sep 01 '20 at 20:50
  • AutoMapper is *not* needed to load data from the database. It's meant to perform very specific, simple mappings. People have been trying to use it for things it was never meant to do, often ending up with *more code* than if they just performed the transformations explicitly. This is such a case. The code in the answer (*not example*) you linked to is *longer* than just creating a new object for a row's contents explicitly, and far more error prone too. – Panagiotis Kanavos Sep 01 '20 at 20:55
  • You could use eg [LINQ to DataTable[(https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/single-table-queries-linq-to-dataset) and get your objects with a very simple LINQ query, eg `someTable.AsEnumerable().Select(row=>new Contract{ ID=row.Field("ID"), Date=row.Field("Date"),...{)` – Panagiotis Kanavos Sep 01 '20 at 20:58
  • because I am not using entity framwork or dapper, in `GetSales` I am calling data access of other project which use ado.net, I even can not refactor it to the latest version because of some reasons ... – eshghi ali Sep 01 '20 at 20:59
  • EF Core and Dapper use ADO.NET too. EF works in ASP.NET just fine, and Dapper works with all runtimes. No matter how you look at it, AutoMapper is simply not meant for this. What does `GetSales()` do, what does it return and why don't you replace that code with eg Dapper? A simple `connection.Query(sqlQuery);` would execute the query *and* map the results in a single call. – Panagiotis Kanavos Sep 01 '20 at 21:01
  • If `GetSales()` returns a DataTable or DataSet the easiest solution would be to use LINQ to DataSet. – Panagiotis Kanavos Sep 01 '20 at 21:02
  • its return datatable and I know that it is possible to use Linq to get what I need, but I was wondering if it is possible with auto mapper, because as I mention in my question I saw serveral examples , but I can not create one my self :) – eshghi ali Sep 01 '20 at 21:07
  • Again, that was an answer, *not an example*. And like *all those attempts* it's way too long and too complex because *AutoMapper isn't meant to do that*. You can tweet its author if you want to get the exact same answer. Or you can red [AutoMapper's design Philosophy](https://jimmybogard.com/automappers-design-philosophy/) where the author expresses his *frustration* with the abuse attempts. AutoMapper is meant to map *Models* to *ViewModels* based on specific conventions. If the conventions don't hold, don't use it – Panagiotis Kanavos Sep 01 '20 at 21:16
  • In the data table's case, *all* conventions are useless. There aren't even any properties to map, one has to explicitly retrieve the column names and types. AutoMapper doesn't know anything about that so the developer has to write all the code by hand – Panagiotis Kanavos Sep 01 '20 at 21:18
  • @PanagiotisKanavos indeed, and if dev uses DataSet designer to create the datatables in design time, can have fully mapped, named objects with 0 lines of code in their `.cs` – Caius Jard Sep 02 '20 at 12:35

1 Answers1

0

You can't use Automapper because it looks at the properties of one object here, and the properties of another object there, and copies them across. A DataRow doesn't have any interesting properties

When you load a base, weakly typed, datatable:

var myDataTable = new DataTable();
myDataDapter.Fill(myDatatable);

You have a collection of DataRow obejcts. When you want an item of data out of a datarow, there are a variety of ways you can get it but the root process is always the same; you get it out of the object array in which it lives by passing an indexer to a collection and casting the result:

var row1 = myDatatable.Rows[0];

var personName = row1["PersonName"] as string; 

There are alternative ways of writing the same thing, but ultimately this data just lives in an array, and is indexed by string/int, it needs casting to be useful. A DataRow has no PersonName property that returns a string; AUtomapper is designed to work with a PersonName property that returns a string.

As Panagiotis has been pointing out in the comments, you'll need to do something with the datarow to make it have properties but the level of effort you go to to prepare an object that you can then use with Automapper, you might as well just skip Automapper and assign the values straight to the destination object

Example

//no
class Person{ 
  public string PersonName 
}
...
foreach(DataRow ro in someDt)
  var p = new Person();
  p.PersonName = ro["PersonName"] as string;

  var p = mapper.Map<PersonDto>(p);
  

All that creating a person class and assigning to its PersonName just so Automapper has something it can map to PersonDto -> pointless! Just make a new PersonDto and assign the value directly


We can make some reduced effort means of performing the same thing, which I suppose might have a use case. If you want to add a DataSet to your project you can then add a new datatable to it and specify the name and type of the properties therein. In code this then becomes a class in its own right, with properties that Automapper can use. In reality this is just Visual Studio writing a bunch of code like:

class PersonRow: DataRow

  public string PersonName {
    get{
      return this["PersonName"] as string;
    }
...

i.e. in inherits base DataRow for you and adds properties. It's a low effort way of creating a data entity representation that still works as a DataTable/DataRow does (because it's inherited).. so a dataadapter / ado.net can fill it, and then you can work with it using the named properties

If you're desperate to use automapper, it'd be the way I'd go; get VS to write this boring code "make a named property that accesses an internal array of object and casts the result" for you. Each one of these things in this picture is a basic DataTable/DataRow, enhanced with named properties:

https://learn.microsoft.com/en-us/visualstudio/data-tools/walkthrough-creating-a-dataset-with-the-dataset-designer?view=vs-2019 (from the docs)

Theyre a lot nicer to work with than base DataTable, you can linq query them directly (personDataTable.Select(x => ...)`, you don't need to cast anything...

Caius Jard
  • 72,509
  • 5
  • 49
  • 80