AutoMapper - mapping child collections in viewmodel - c#

I have a viewmodel that needs to display a certain IEnumerable field as semicolon-separated textbox. At first I thought of using DefaultModelBinder to transform it, but I had trouble thinking how to achieve it in both directions (dto <-> viewmodel).
Nicknames is the field I'm trying to display as one textbox separated by semicolon.
public class Parent
{
public IEnumerable<Child> Children { get; set; }
}
public class Child
{
public IEnumerable<string> Nicknames { get; set; }
}
So I decided to try AutoMapper, I created two ViewModels:
public class ParentViewModel
{
public IEnumerable<ChildViewModel> Children { get; set; }
}
public class ChildViewModel
{
public string Nicknames { get; set; }
}
Then, I created mappings, like this for the children (omitted the other-way conversion for brevity)
Mapper.CreateMap<Child, ChildViewModel>().ForMember(
d => d.Nicknames, o => o.ResolveUsing<ListToStringConverter>().FromMember(s => s.Nicknames);
Then, for the parent, created a naive map (again, omitted the other-way)
Mapper.CreateMap<Parent, ParentViewModel>();
I truly expected the child mappings occur automatically, but they don't, I've already created too much "proper" code to solve a really simple problem which in any other simpler/older non-MVC environment, I'd be done with a long time ago :) How can I proceed and tell AutoMapper to transform the children without writing another "children member resolver".
Have I overthought this and there's a simpler way?
Thank you!

try
Mapper.CreateMap<Parent, ParentViewModel>();
Mapper.CreateMap<Child, ChildViewModel>();
var v = Mapper.Map<Parent, ParentViewModel>(parent);

Found this solution https://stackoverflow.com/a/7555977/1586498, that works for me:
Mapper.CreateMap<ParentDto, Parent>()
.ForMember(m => m.Children, o => o.Ignore()) // To avoid automapping attempt
.AfterMap((p,o) => { o.Children = ToISet<ChildDto, Child>(p.Children); });
The ToISet function is defined in the above link.
Simpler examples 'just work' in LinqPad - so more investigation is required.
A complete listing of a working program:
public class Child{ public string Name {get; set; }}
public class ChildDto{ public string NickName {get; set; }}
public class Parent{ public virtual IEnumerable<Child> Children {get; set; }}
public class ParentDto{ public IEnumerable<ChildDto> Kids {get; set; }}
private static void Main()
{
AutoMapper.Mapper.CreateMap<Parent, ParentDto>().ForMember(d=>d.Kids, opt=>opt.MapFrom(src=>src.Children));
AutoMapper.Mapper.CreateMap<Child, ChildDto>().ForMember(d=>d.NickName, opt=>opt.MapFrom(src=>src.Name));
var pList = new HashSet<Parent>{
new Parent{ Children = new HashSet<Child>{new Child{Name="1"}, new Child{Name="2"}}},
new Parent{ Children = new HashSet<Child>{new Child{Name="3"}, new Child{Name="4"}}},
};
var parentVm = AutoMapper.Mapper.Map<IEnumerable<Parent>, IEnumerable<ParentDto>>(pList);
parentVm.Dump();
}

Related

C# Automapper IQueryable - LINQ 2 SQLite - Query returns only parent, nested child always null

Hi people of the internet,
I've been searching on SO for a couple of days now, tried loads of possible solutions but I'm not able to solve my issue in the following test-project. Please excuse my lack of skill as I'm a self-taught programmer for just over a year now and I'm this is my first question on SO.
The situation:
I've got 2 entity classes Car and Wheel and want to store and query them from a single db table using c#'s linq to sql(ite).
public class Car
{
public Int64? Id { get; private set; }
public string Manufacturer { get; private set; } = "Porsche";
public double Mileage { get; private set; }
public Wheel Wheel { get; private set; }
public Car()
{
this.Id = Program.CarCount;
Mileage = new System.Random(Program.CarCount).Next(0, 500000);
Program.CarCount++;
}
public Car CreateWheels()
{
// Adds Wheel 2 Car
}
}
public class Wheel
{
public Int64? Id { get; set; }
public string Manufacturer { get; set; } = "Continental";
public Wheel() { Id = Program.WheelCount; }
}
Unfortunately as it is a test project for a different project I'm working on I'm not able to split up the raw-data into multiple OR-tables.
To work with a single table instead I flatten them into a single CarDto class that matches my single db table.
I flatten both classes into the CarDto to be used with LINQ 2 SQLite using AutoMap and assigning the individual db columns.
[Table(Name = "Cars")]
[AutoMap(typeof(Car), ReverseMap = true)]
public class CarDto
{
public CarDto(){ }
[Column(IsPrimaryKey = true)]
public Int64? Id { get; set; }
[Column]
public string Manufacturer { get; set; }
[Column]
public double Mileage { get; set; }
[Column]
public Int64? WheelId { get; set; }
[Column]
public string WheelManufacturer { get; set; }
}
Mapping between DTO-class and entity-class is done with a mapper CarMapper using AutoMapper v.9x
public class CarMapper
{
public IMapper Map => Config.CreateMapper();
IConfigurationProvider Config;
public CarMapper()
{
Config = new MapperConfiguration(cfg =>
{
cfg.AddMaps(typeof(CarMapper).Assembly);
});
Config.AssertConfigurationIsValid();
}
}
Interaction with the db is handled via a repository class CarRepocontaining the mapper and the DataContext class of C# Linq2SQL
public class CarContext : DataContext
{
public Table<CarDto> CarDtos;
public CarContext(IDbConnection connection) : base(connection) { }
}
public class CarRepo
{
private string conn;
CarMapper mp = new CarMapper();
CarContext CarContext => new CarContext(new SQLiteConnection(conn));
public IQueryable<Car> Cars { get => Qry(); }
public CarRepo(string connectionString) { this.conn = connectionString; }
private IQueryable<Car> Qry()
{
return mp.Map.ProjectTo<Car>(CarContext.CarDtos);
}
public List<Car> GetAllCarsFromDb()
{
var dtosFromDb = new List<CarDto>();
using (var db = CarContext)
return mp.Map.Map<List<CarDto>, List<Car>>(db.CarDtos.ToList());
}
public void InsertCars(List<Car> cars)
{
using (var db = CarContext)
{
db.CarDtos.InsertAllOnSubmit(mp.Map.Map<List<Car>, List<CarDto>>(cars));
db.SubmitChanges();
}
}
}
The problem:
Insertion and mapping the CarDto-table to Car works flawlessly. Running repo.Cars.Where(car => car.Id <= 5).ToList(); results as expected except for the nested object Wheel, which always returns as null. The problem exists when projecting a car-query and probably lies in the mapping of the query expression. Looking at the resulting query this seems obvious.
SELECT[t0].[Id], [t0].[Manufacturer], [t0].[Mileage]
FROM[Cars] AS[t0]
WHERE[t0].[Id] <= #p0
Running the following mapping mp.Map.Map<List<CarDto>, List<Car>>(db.CarDtos.ToList()); works and delivers Car including Wheel instances.
I tried loads of solutions (custom mappings,... ) and went through the AutoMapper documentation but wasn't able to fix my problem. Hope someone can help me.
Cheers from germany!
Henrik
EDIT:
P.s. I added a gist https://gitlab.com/snippets/1957648
https://gist.github.com/henrikherr/29eb2913d403ab1d6bede52ed011869a
As #LucianBargaoanu pointed out the ProjectTo doesn't work with ForPath. He also correctly pointed out that the logic/usage of my dto and entities is reversed.
In addition I wouldn't have to do the iqueryable-mapping if my db-design would match my Carand Wheel classes in a more class/object-related way or if my repo would expose and handle the dto class.
You're relying implicitly on ForPath here and that doesn't work with ProjectTo. But you entities/dtos are exactly reversed. Car should be in the DB, with a Wheel FK entity. And CarDto would show up in the UI, or whatever uses the DB. – Lucian Bargaoanu

AutoMapper - ProjectTo class with multiple properties of the same type

Recently found out about AutoMapper's ProjectTo<> method, so I've been playing around with it.
So far so good until I came upon a class that had multiple properties of the same type, such as:
public class RandomDto
{
public int Id {get;set;}
public ChildDto FirstChild {get;set;}
public ChildDto SecondChild {get;set;}
}
It seems like it generates SQL for a single Child relationship, and not for both:
SELECT CASE
WHEN [dtoRandom].[FirstChild_FK] IS NULL
THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END, [t0].[Child_Description]
END, [dtoRandom].[Id]
FROM [Randoms] AS [dtoRandom]
INNER JOIN (
SELECT [s].*
FROM [Childs] AS [s]
) AS [t0] ON [dtoRandom].[FirstChild_FK] = [t0].[Id]
I've tried
.ProjectTo<RandomDto>(null, "FirstChild", "SecondChild")
.ProjectTo<RandomDto>(x=>x.FirstChild, x=>x.SecondChild)
and both return with the first one being filled in, second being null
Not sure if I need to set custom aliases or something for this to work?
I experienced the same behaviour and tried to work around the problem by using an other class for the second navigation property that only derived from the original one.
public class ChildDtoTmp : ChildDto { }
public class RandomDto
{
public int Id { get; set; }
public ChildDto FirstChild { get; set; }
public ChildDtoTmp SecondChild { get; set; }
}
This worked fine, but because the type of the navigation proerty has another navigation property the same strange behaviour shows up with the nested navigation property.
public class ChildDto
{
public int Id { get; set; }
public InnerChildDto InnerChild { get; set; }
}
This leads to the properties FirstChild and SecondChild being mapped, but only the InnerChild property of the FirstChild gets mapped.
Maybe this helps someone to figure out how to solve this.
Your issue might be related to a slightly confusing option called MaxDepth. I've managed to reproduce that exact problem by calling MaxDepth(1), which in my understanding should only affect self-referencing entities such as:
class Foo
{
public Foo InnerFoo { get; set; }
}
In that case, a MaxDepth(1) should only map the first Foo found in a object graph. Which is exactly what it happens, but it also affects the following structure (quite wrongly, IMHO):
class Bar
{
public Foo Foo1 { get; set; }
public Foo Foo2 { get; set; }
}
A MaxDepth(1) in the above scenario will map only the Foo1 property, keeping Foo2 as null.
Oh, by the way: to set MaxDepth, one may apply to all their mappings:
var mapperCfg = new MapperConfiguration(cfg =>
{
cfg.AddProfile<SomeProfile>();
cfg.ForAllMaps(SetMaxDepth);
});
private static void SetMaxDepth(TypeMap typeMap, IMappingExpression expression) => expression.MaxDepth(1);
Or to each map individually:
var mapperCfg = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SomeObject, SomeObjectDto>().MaxDepth(1);
});

Config Automapper to ignore type when it's an inner-inner property but not inner property

This one takes a little explaining. I have a set of types such that;
public class Child
{
public int ID { get; set;}
}
public class MayHaveChild
{
public Child Value { get; set; }
public int MayID { get; set; }
}
public class MustNotHaveChild { get; set; }
{
public List<MayHaveChild> MayValues { get; set; }
}
In the above scenario, I want any mapping of MayHaveChild to have the values for the Child object, except when I have mapped MustNotHaveChild. E.g.;
When I have
//...some code
MayHave obj = Mapper.Map<MayHaveChild>(childObj);
// I want to be able to access obj.Child.ID
But when I have
//...some code
MustNotHave obj = Mapper.Map<MustNotHaveChild>(notHaveObj);
// I want to be able to access obj.MayValues[0].MayID but
// *not* obj.MayValues[0].Value
I've been through the automapper documention on nesting, polymorphism, lists, etc and I can't find anything that quite matches what I want.
I could solve this by having a inheriting the MayHave class to a MustNotHave variant but this would involve changing quite a lot of existing code. Is there a way to configure Automapper in the manner I need?
I couldn't find a way to configure AutoMapper the way I wanted without going down the inheritance route - though this proved less problematic than I thought. I did something like the following;
public class NoChild : MayHaveChild
{
}
public class MustNotHaveChild { get; set; }
{
// \/--datatype change here
public List<NoChild> MayValues { get; set; }
}
Then, later in the AutoMapper config;
Mapper.CreateMap<MayHave, NoChild>()
.ForMember(c => c.Child, opt => opt.Ignore());

AutoMapper inheritance and Linq

I've been looking over how to use Inheritance in AutoMapper but I'm struggling to get it working fully with Linq. Here is my code:
I have defined my mappings here:
CreateMap<Article, ArticleDetailsViewModel>()
.Include<Article, ArticleNewsItemDetailsViewModel();
CreateMap<Article, ArticleNewsItemDetailsViewModel>();
ArticleDetailsViewModel is a base class of ArticleNewsItemDetailsViewModel.
Now here lies the problem, if I had:
CreateMap<ArticleNewsItem, ArticleNewsItemDetailsViewModel>();
All of the properties in the view model would automatically map because they are the same name as their Linq object counterpart. However, because I am using the Article => ArticleNewsItemDetailsViewModel mapping this is not possible, instead I would have to define each one as:
.ForMember(x => x.Property1, opt => opt.MapFrom(src => src.ArticleNewsItem.Property1)
I thought about moving all properties from ArticleNewsItemDetailsViewModel into a new view model and having that class a property within the ArticleNewsItemDetailsViewModel and as long as there is a mapping between those two objects then it will work, but it doesn't feel very clean.
Is there any way to avoid having to do this?
Supposing you have the following classes:
public class Article
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public ArticleNewsItem ArticleNewsItem { get; set; }
}
public class ArticleDetailsViewModel
{
public string Prop1 { get; set; }
}
public class ArticleNewsItemDetailsViewModel : ArticleDetailsViewModel
{
public string Prop2 { get; set; }
public string Prop3 { get; set; }
}
public class ArticleNewsItem
{
public string Prop3 { get; set; }
}
The mapping should look like below:
var res = Mapper.Map<Article, ArticleNewsItemDetailsViewModel>(_article);
Mapper.Map(_article.ArticleNewsItem, res);
Moreover you can create custom type converter to avoid writing these two lines every time you need to map Article to ArticleNewsItemDetailsViewModel.
Apologies if I am over simplifying this in my head but can't you simply add the direct mapping you mention:
CreateMap<ArticleNewsItem, ArticleNewsItemDetailsViewModel>();
To me this is the simplest and cleanest solution...
EDIT
Sorry, I misunderstood. You can't map an object to a nested property without creating a custom map via .ConstructUsing() or .ConvertUsing() methods (or doing it the untidy way)...
Mapper.CreateMap<Article, ArticleNewsItemDetailsViewModel>().ConstructUsing(ConstructItem)
..Then create your method to build the ArticleNewsItemDetailsViewModel...
private static ArticleNewsItemDetailsViewModel ConstructItem(Article source)
{
var newsItem = new ArticleNewsItem
{
Prop1 = source.Prop1,
Prop2 = source.Prop2
};
var result = new ArticleNewsItemDetailsViewModel()
{
ArticleNewsItem = newsItem
};
return result;
}
However I would still recommend re implementing your solution so you are mapping 'like for like'. Here is a good example: http://automapper.codeplex.com/wikipage?title=Nested%20Mappings
Assuming all the required properties are in Article you could create a Custom Value Resolver to do this e.g.
public class ArticleNewsItemResolver : ValueResolver<Article, ArticleNewsItem>
{
protected override ArticleNewsItem ResolveCore(Article source)
{
return Mapper.DynamicMap<Article, ArticleNewsItem>(source);
}
}
...
CreateMap<Article, ArticleNewsItemDetailsViewModel>()
.ForMember(src => src.NewsItem, opt => opt.ResolveUsing<ArticleNewsItemResolver>());

How to map a viewModel to a hierarchy model with arrays with AutoMapper?

A deep model is code-generated with lots of arrays (think WCF proxy genererated code based on a wsdl) that needs to be filled with a flattened viewmodel. There are no naming conventions between the 2 models.
The flat model looks for example like this:
public class ViewModel
{
public string Item1 { get; set; }
public string Item2 { get; set; }
}
The deep model looks for example like this:
public class DeepLevel0
{
public DeepLevel1 Level1 { get; set; }
}
public class DeepLevel1
{
public string Prop1;
public DeepLevel2[] Level2 { get; set; }
}
public class DeepLevel2
{
public string Prop2;
public string Prop3;
}
The end mapping result should be the following
DeepLevel0.Level1.Prop1 = ViewModel.Item1
DeepLevel0.Level1.Level2[0].Prop2 = ViewModel.Item2
DeepLevel0.Level1.Level2[0].Prop2 = null;
I really like the validation system in AutoMapper, knowing that you tackled all properties.
I got the following working (but loosing the validation):
Mapper.CreateMap<ViewModel, DeepLevel0>()
.ForMember(d => d.Level1, opt => opt.MapFrom(s =>
new DeepLevel1 {
Prop1 = s.Item1,
Level2 = new[]
{
new DeepLevel2
{
Prop2 = s.Item2,
Prop3 = null
}
}
}));
}
Is there an other better way ?
No I don't think so. You can always switch to using a constructor for the DeepLevel objects which might tidy them up a bit.

Categories