1

I have list with items contained self reference. For example:
Model

public class Stock{
  public int StockId { get; set; }
  public int? ParentStockId { get; set; }
  public int Level { get; set; }
  public string Name { get; set; }
}

ViewModel

public class StockViewModel {
  public int Level { get; set; }
  public string Name { get; set; }
  public IEnumerable<StockViewModel> SubStock { get; set; }
}

Sample data

var stocks = new List<Stock> {
    new Stock{ StockId = 1, Level = 10, Name = "Root1" },
    new Stock{ StockId = 2, Level = 10, Name = "Root2" },
    new Stock{ StockId = 3, ParentStockId = 1, Level = 20, Name = "Area1" },
    new Stock{ StockId = 4, ParentStockId = 2, Level = 20, Name = "Area2" },
    new Stock{ StockId = 5, ParentStockId = 3, Level = 30, Name = "Box11" },
    new Stock{ StockId = 6, ParentStockId = 3, Level = 30, Name = "Box12" },
    new Stock{ StockId = 7, ParentStockId = 4, Level = 30, Name = "Box21" }
};

I use a similar code to achieve the desired result. But I'm sure it's wrong. I have to make a configuration before each new request.

        var mapper = new Mapper(); 
        TypeAdapterConfig<Stock, StockViewModel>
            .NewConfig()
            .Map(dst => dst.SubStock, src => stocks.Where(x => x.ParentStockId == src.StockId).ToList());
        var viewStocks = mapper.Map<IEnumerable<Stock>, IEnumerable<StockViewModel>>(stocks.Where(x => x.ParentStockId == null));
        // Sample output
        foreach (var stock in viewStocks){
            Console.WriteLine(stock.Level.ToString() + " " + stock.Name);
            if (stock.SubStock != null) 
                foreach (var stock1 in stock.SubStock){
                    Console.WriteLine("  " + stock1.Level.ToString() + " " + stock1.Name);
                    if (stock1.SubStock != null) 
                        foreach (var stock2 in stock1.SubStock){
                            Console.WriteLine("    " + stock2.Level.ToString() + " " + stock2.Name);
                        }
                }
        }

Fiddle
How to map viewStocks hierarhical? Same as

10 Root1
  20 Area1
    30 Box11
    30 Box12
10 Root2
  20 Area2
    30 Box21

2 Answers2

2

You don't need to configure Mapster config for each new request. According to https://github.com/MapsterMapper/Mapster/wiki/Config-location:

Configuration should be set only once and reuse for mapping. Therefore, we should not keep configuration and mapping in the same location.Configuration should keep in entry point such as Main function or Global.asax.cs or Startup.cs.

So that you can move that mapping to entry point:

TypeAdapterConfig<Stock, StockViewModel>
    .NewConfig()
    .Map(dst => dst.SubStock, src => stocks.Where(x => x.ParentStockId == src.StockId).ToList());

Furthermore, in order to seperate your config and mapping instance instead of this

 var viewStocks = mapper.Map<IEnumerable<Stock>, IEnumerable<StockViewModel>>(stocks.Where(x => x.ParentStockId == null));

I prefer to use Adapt as follows :

var socksWithoutParent = stocks.Where(x => x.ParentStockId == null);
var viewStocks = socksWithoutParent.Adapt<List<StockViewModel>>();

Full example Fiddle, it will give you same output:

10 Root1
  20 Area1
    30 Box11
    30 Box12
10 Root2
  20 Area2
    30 Box21
Selim Yildiz
  • 5,254
  • 6
  • 18
  • 28
  • I understand that you need to separate the configuration and mapping. However, I do not have access to `stocks` in the configuration. how to pass `stocks` to mapping and use it in configuration? – Evgene Sofronov Jun 15 '20 at 04:59
  • As I pointed out you don't need to access this stocks configuration: `var viewStocks = mapper.Map, IEnumerable>(stocks.Where(x => x.ParentStockId == null));` just remove this configuration and use `Adapt` – Selim Yildiz Jun 15 '20 at 05:56
  • In my project on .net core 2.1 `stocks`(StocksContorller.cs) exists only in action. In configuration(startup.cs) `stocks` doesn't exists and i don't write `TypeAdapterConfig .NewConfig() .Map(dst => dst.SubStock, src => stocks.Where(x => x.ParentStockId == src.StockId).ToList());` – Evgene Sofronov Jun 15 '20 at 06:03
  • I really don't understand why you can't write `TypeAdapterConfig .NewConfig() .Map(dst => dst.SubStock, src => stocks.Where(x => x.ParentStockId == src.StockId).ToList());` in startup.cs. You just need add namespace of `Stock` and `StockViewModel` in order to write that configuration, that's all. – Selim Yildiz Jun 15 '20 at 19:20
  • Take a look at https://stackoverflow.com/questions/58822037/where-to-put-mapster-mapping-code-in-ef-core-3-and-asp-net-mvc-core how to write configuration in startup. – Selim Yildiz Jun 15 '20 at 19:26
  • Your answer gave me an idea. [Fiddle](https://dotnetfiddle.net/Cbxuem) in this example, I can correctly separate the configuration and map – Evgene Sofronov Jun 17 '20 at 12:33
1

For correctly separate the configuration and map, I passing a parameter with a stocks

var viewStocks = stocks.Where(x => x.ParentStockId == null)
            .BuildAdapter()
            .AddParameters("paramKey", stocks)
            .AdaptToType<List<StockViewModel>>();

and my config

TypeAdapterConfig<Stock, StockViewModel>
            .NewConfig()
            .Map(dst => dst.SubStock, 
                 src => ((IEnumerable<Stock>)MapContext.Current.Parameters["paramKey"])
                 .Where(x => x.ParentStockId == src.StockId)
                 .BuildAdapter()
                 .AddParameters("paramKey", MapContext.Current.Parameters["paramKey"])
                 .AdaptToType<List<StockViewModel>>());

full example

Now in my configuration i don't need to know about stocks