ReverseMap on open generics - automapper

I'm using AutoMapper 4.1.1 (latest stable release on NuGet), and it looks like using ReverseMap on an open generic CreateMap doesn't work.
Sample program:
static void Main(string[] args)
{
//Mapper.CreateMap(typeof(A<>), typeof(B<>)).ReverseMap(); // Doesn't work.
Mapper.CreateMap(typeof(A<>), typeof(B<>)); // Works.
Mapper.CreateMap(typeof(B<>), typeof(A<>)); // Works.
Mapper.CreateMap<AData, BData>().ReverseMap();
var b = new B<BData> { Data = new BData { Info = "Test" } };
var a = Mapper.Map<A<AData>>(b);
}
public class A<T>
{
public T Data { get; set; }
}
public class B<T>
{
public T Data { get; set; }
}
public class AData
{
public string Info { get; set; }
}
public class BData
{
public string Info { get; set; }
}
If I use the line with ReverseMap on it, I get the following exception:
Missing type map configuration or unsupported mapping.
Mapping types:
B`1 -> A`1
ConsoleApplication64.Program+B`1[[ConsoleApplication64.Program+BData, ConsoleApplication64, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] -> ConsoleApplication64.Program+A`1[[ConsoleApplication64.Program+AData, ConsoleApplication64, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Destination path:
A`1
Source value:
ConsoleApplication64.Program+B`1[ConsoleApplication64.Program+BData]
Am I doing something wrong, or can we actually not use ReverseMap with open generics?

Related

Mapping null source values into destination

When mapping a source instance to a destination instance, how are null source values handled? It appears that with built in types, a null source value will overwrite a destination value (set it to null). However with a navigation property, a destination value will not be set to null by a null source value, e.g. OuterDest.Innter:
`
public class OuterSource
{
public int Value { get; set; }
public InnerSource? Inner { get; set; }
}
public class InnerSource
{
public int OtherValue { get; set; }
}
public class OuterDest
{
public int Value { get; set; }
public InnerDest? Inner { get; set; }
}
public class InnerDest
{
public int OtherValue { get; set; }
}
`
This test will fail
[TestMethod]
public void NestedTestNullSourceValue()
{
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<OuterSource, OuterDest>();
cfg.CreateMap<InnerSource, InnerDest>();
});
var mapper = mappingConfig.CreateMapper();
OuterSource source = new()
{
Value = 123
};
OuterDest dest = new()
{
Value = 888,
Inner = new()
{
OtherValue = 999
}
};
mapper.Map(source, dest);
Assert.AreEqual(123, dest.Value);
Assert.IsNull(dest.Inner);
}
Had the same issue (AutoMapper - Map(source, destination) overwrite destination child object with null value from source via configuration), updating to AutoMapper 12.0.1 pre-release solved my issue

IncludeMembers of Automapper not works as expected

According to automapper docs, I can map nested objects to destination using IncludeMembers function. I have issues with next sample.
Code is available on net fiddle, below is quick reference:
How I map:
var source = new CategoryStatus
{
Subgroup = new CategorySubgroup
{
SubgroupCode = "SubgroupCode",
CategoryGroup = new CategoryGroup { GroupCode = "SubgroupCode" }
}
};
var result = Mapper.Map<Dest, CategoryStatus>(source);
My classes:
public class Dest
{
public string SubgroupCode { get; set; }
public string GroupCode { get; set; }
}
public class CategoryStatus
{
public CategorySubgroup Subgroup { get; set; }
}
public class CategorySubgroup
{
public string SubgroupCode { get; set; }
public CategoryGroup CategoryGroup { get; set; }
}
public class CategoryGroup
{
public string GroupCode { get; set; }
}
My Configuration:
var cfg2 = new MapperConfiguration(cfg => {
cfg.CreateMap<CategoryStatus, Dest>()
.IncludeMembers(x => x.Subgroup);
cfg.CreateMap<CategoryGroup, Dest>();
cfg.CreateMap<CategorySubgroup, Dest>()
.IncludeMembers(x => x.CategoryGroup);
});
Error:
[System.ArgumentException: Property 'System.String GroupCode' is not defined for type 'CategorySubgroup']
Any ideas about configuration setup? Automapper version is 10.0.0
Update
Version 9.0.0 works. Possible latest will also work, but it contains some breaking changes for me, so I didn't test it.

EF Core Collections using Automapper.Collection.EntityFrameworkCore

Given I have 2 classes, Foo and Bar:
public class Foo
{
private readonly List<Bar> _bars = new List<Bar>();
public int Id { get; private set; }
public string Name { get; private set; }
public IEnumerable<Bar> Bars => _bars;
public void AddBar(Bar bar)
{
_bars.Add(bar);
}
public static Foo Create(string name)
{
return new Foo { Name = name };
}
private Foo() { }
}
public class Bar
{
public int Id { get; private set; }
public string Description { get; private set; }
public static Bar Create(string description)
{
return new Bar { Description = description };
}
}
With 2 corresponding DTOs,
public class BarDto
{
public int Id { get; set; }
public string Description { get; set; }
}
public class FooDto
{
public int Id { get; set; }
public string Name { get; set; }
public List<BarDto> Bars { get; set; }
public FooDto()
{
Bars = new List<BarDto>();
}
}
And an AutoMapper/AutoMapper.Collection.EntityFrameworkCore setup of
var config = new MapperConfiguration(cfg =>
{
cfg.AddCollectionMappers();
cfg.UseEntityFrameworkCoreModel<DemoContext>();
cfg.CreateMap<BarDto, Bar>().EqualityComparison((src, dest) => src.Id == dest.Id);
cfg.CreateMap<FooDto, Foo>().ForMember(dest => dest.Bars, opt =>
{
opt.MapFrom(s => s.Bars);
opt.UseDestinationValue();
}).EqualityComparison((src, dest) => src.Id == dest.Id);
});
I have a use case whereby the incoming FooDto may contain inserted, appended, updated and deleted items in the Bars collection which I am attempting to handle by:
Looking up the existing entity from the database
Mapping changes from the DTO to the entity
Saving the changes to the database
However the following code produces an InvalidOperationException exception stating that "The instance of entity type 'Bar' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached"
var fooToUpdate = db.Foos.Include(_ => _.Bars).FirstOrDefault(_ => _.Id == fooDto.Id);
mapper.Map(fooDto, fooToUpdate);
db.SaveChanges();
My understanding was that becuase I am setting EqualityComparison for the BarDto -> Bar mapping it should update the tracked entity and the save operation should succeed becuase it was referencing the same object?
I am not sure if I'm going about this the wrong way or simply missing somthing in the configuration.
Update
It seems the problem I am facing may be related to this issue on github.

AutoMapper .ReverseMap() .Ignore() not working

Having an issue with version 6.1.1. In the below, the result of the reverse map still has the Company object populated. Per this post, which shows what I am doing below, except they are ignoring a property, and I'm ignoring a complex object.
What am I missing?
CreateMap<Item, ItemViewModel>(MemberList.Destination)
.ReverseMap()
.ForMember(x => x.Company, x => x.Ignore())
;
With AutoMapper 6.1 you could use ForPath instead ForMember to ignore complex objects.
See How to ignore property with ReverseMap for further information.
I see not what is wrong, but here is a running sample:
namespace AutomapperTest2
{
internal class Program
{
#region Methods
private static void Main(string[] args)
{
// Configure the mappings
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ApplicantEducation, ApplicantEducationVM>();
cfg.CreateMap<Applicant, ApplicantVM>().ReverseMap()
.ForMember(x => x.Education, x => x.Ignore());
});
var config = new MapperConfiguration(cfg => cfg.CreateMissingTypeMaps = true);
var mapper = config.CreateMapper();
Applicant ap = new Applicant
{
Name = "its me",
Education =
new ApplicantEducation
{
SomeInt = 10,
SomeString = "sampleString"
}
};
// Map
ApplicantVM apVm = Mapper.Map<Applicant, ApplicantVM>(ap);
Applicant apBack = Mapper.Map<ApplicantVM, Applicant>(apVm);
}
#endregion
}
/// Your source classes
public class Applicant
{
public ApplicantEducation Education { get; set; }
public string Name { get; set; }
}
public class ApplicantEducation
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
}
// Your VM classes
public class ApplicantVM
{
public string Description { get; set; }
public ApplicantEducationVM Education { get; set; }
public string Name { get; set; }
}
public class ApplicantEducationVM
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
}
}
}

AutoMapper Custom Type Converter not working

I am using Troy Goode's PagedList to provide paging information in my WebApi. His package returns an IPagedList that implements IEnumerable but also contains custom properties such as IsLastPage, PageNumber, PageCount, etc.
When you try to return this class from a WebApi controller method (such as the GET), the Enumerable is serialized, but the custom properties are not. So, I thought I would use AutoMapper and write a custom type converter to convert to a class such as this:
public class PagedViewModel<T>
{
public int FirstItemOnPage { get; set; }
public bool HasNextPage { get; set; }
public bool HasPreviousPage { get; set; }
public bool IsFirstPage { get; set; }
public bool IsLastPage { get; set; }
public int LastItemOnPage { get; set; }
public int PageCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalItemCount { get; set; }
public IEnumerable<T> rows { get; set; }
}
Since I move the Enumerable into a distinct property, the serialization handles it perfectly. So, I sat down and wrote a custom type converter like this:
public class PagedListTypeConverter<T> : ITypeConverter<IPagedList<T>, PagedViewModel<T>>
{
public PagedViewModel<T> Convert(ResolutionContext context)
{
var source = (IPagedList<T>)context.SourceValue;
return new PagedViewModel<T>()
{
FirstItemOnPage = source.FirstItemOnPage,
HasNextPage = source.HasNextPage,
HasPreviousPage = source.HasPreviousPage,
IsFirstPage = source.IsFirstPage,
IsLastPage = source.IsLastPage,
LastItemOnPage = source.LastItemOnPage,
PageCount = source.PageCount,
PageNumber = source.PageNumber,
PageSize = source.PageSize,
TotalItemCount = source.TotalItemCount,
rows = source
};
}
}
And then set it up in my configuration like this:
Mapper.CreateMap<IPagedList<Department>, PagedViewModel<Department>>().ConvertUsing(new PagedListTypeConverter<Department>());
But, when I try to call it like this:
var x = Mapper.Map<IPagedList<Department>, PagedViewModel<Department>>(departments);
I get this error:
Missing type map configuration or unsupported mapping.
Mapping types: IPagedList1 -> PagedViewModel1
PagedList.IPagedList1[[Provision.DomainObjects.Department,
Provision.DomainObjects, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null]] ->
Provision.DomainObjects.PagedViewModel1[[Provision.DomainObjects.Department,
Provision.DomainObjects, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null]]
Destination path: PagedViewModel`1
Source value:
PagedList.StaticPagedList`1[Provision.DomainObjects.Department]
How can I make this work?
After pulling my hair out, I finally figured this one out. There isn't anything at all wrong with the code. It turned out to be a threading issue where the configured mappings were getting cleared out. The code above is the proper way to do what I wanted. I am leaving this here so that I can point another question to it for others who need to do the same thing.

Resources