Automapper causing stack overflow - c#

I am using Automapper to map two classes:
public partial class db_MyObject
{
public int Id { get; set; }
public Nullable<int> ParentId { get; set; }
}
public class MyObject
{
public int Id { get; set; }
public MyObject Parent { get; set; } // Parent can be null
}
How can I configure this mapping. I tried this:
Mapper.CreateMap<db_MyObject, MyObject>()
.ForMember(dest => dest.Parent, opt => opt.ResolveUsing(model => new db_MyObject() {
Id = model.ParentId ?? 0 }));
and it caused stack overflow.

Shouldn't it be
Mapper.CreateMap<db_MyObject, MyObject>()
.ForMember(
dest => dest.Parent,
opt => opt.ResolveUsing(model => new MyObject() {
Id = model.ParentId ?? 0 })
);
I.e. new MyObject() instead of new db_MyObject(), because Parent is a MyObject?

Related

Automapper ignores the mapping for derived class

I have a class hierarchy as described below:
public partial class EmployeeDTO
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
public virtual string ReportsTo { get; set; }
}
public class ManagerDTO: EmployeeDTO
{
public override string ReportsTo
{
get => null;
set => throw new InvalidOperationException("ReportsTo can not be set to other than the preset value.");
}
}
public class EmployeeModel
{
public long Id { get; set; }
public string FullName { get; set; }
public EmployeeType Type { get; set; }
public string ReportsTo { get; set; }
}
public enum EmployeeType
{
Employee = 0,
Manager = 1,
}
Mapping configuration
CreateMap<EmployeeDTO, EmployeeModel>()
.ForMember(empmodel => empmodel.FullName, empdto => empdto.MapFrom(empdto => empdto.Name))
.ForMember(empmodel => empmodel.Type, empdto => empdto.MapFrom(empdto => EmployeeType.Employee));
CreateMap<ManagerDTO, EmployeeModel>()
.ForMember(model => model.FullName, dto => dto.MapFrom(dto => dto.Name))
.ForMember(model => model.Type, dto => dto.MapFrom(dto => EmployeeType.Manager));
CreateMap<EmployeeModel, ManagerDTO>()
.ForMember(dto => dto.Name, model => model.MapFrom(model => model.FullName))
.ForMember(t => t.ReportsTo, t => t.Ignore());
CreateMap<EmployeeModel, EmployeeDTO>()
.ForMember(dto => dto.Name, model => model.MapFrom(model => model.FullName));
I am trying to patch the entity as follows:
[HttpPatch]
public IActionResult Patch([FromBody] Delta<EmployeeDTO> entityDelta)
{
var existingModel = new EmployeeModel
{
Id = 123,
FullName = "XYZ",
Type = Shared.EmployeeType.Manager,
ReportsTo = null
};
var existingEntity = _mapper.Map<EmployeeDTO>(existingModel);
entityDelta.Patch(existingEntity);
// Following mapping incorrectly sets type of updatedModel as EmployeeType.Employee
var updatedModel = _mapper.Map(existingEntity, existingModel);
return Ok(updatedModel);
}
After executing the above code expected type of the updatedModel to be Manager, but the type is Employee instead.
I have tried using the other overloads for the Map. Map(entity) works as expected when explicit type is provided for the previous mapping ie. Map(model). However, this overload creates another instance of the dbmodel with the same id, which leads to further issues.
To avoid having two runtime DbEntries with the same id, I am trying to use the Map(entity, existimgModel) overload of the mapper, which is failing to give the desired outcome.
Any ideas on how to solve this?
Sample code

How to assign null for the object if object members have no value - automapper c#

I am using automapper in c#.
class A
{
public int Value { get; set; }
public string Code { get; set; }
public B? Details { get; set; }
}
class B
{
public int Id { get; set;}
public string Name { get; set; }
}
class C
{
public int Value { get; set; }
public string Code { get; set; }
public int? DetailId { get; set; }
public string? DetailName { get; set; }
}
And in automapper I used like below:
CreateMap<C, A>()
.ForPath(o => o.Details.Id, b => b.MapFrom(z => z.DetailId))
.ForPath(o => o.Details.Name, b => b.MapFrom(z => z.DetailName))
.ReverseMap();
When I using like the above mapping, I get like output as
"details ": {
"id": 0,
"name": ""
}
I need to get Details value as null instead of an object type if it's members have no value. i.e) DetailId and DetailName have no value. How to get this?
"details" : null
You can user Conditional Mapping
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<C, B>()
.ForMember(o => o.Id, b => b.MapFrom(z => z.DetailId))
.ForMember(o => o.Name, b => b.MapFrom(z => z.DetailName));
cfg.CreateMap<C, A>()
.ForMember(o => o.Details, b => b.MapFrom((c, a, obj, context) => !string.IsNullOrEmpty(c.DetailName) ? context.Mapper.Map<B>(c) : null))
.ReverseMap();
});
You could do this with an AutoMapper after map action.
Something like this:
CreateMap<C, A>()
.ForPath(o => o.Details.Id, b => b.MapFrom(z => z.DetailId))
.ForPath(o => o.Details.Name, b => b.MapFrom(z => z.DetailName))
.AfterMap((src, dest) =>
{
dest.Details = src.DetailId.HasValue && src.DetailName != null
? dest.Details
: null;
})
.ReverseMap());
You can use IValueResolver interface to achieve what you require. Documentation: http://docs.automapper.org/en/stable/Custom-value-resolvers.html
Also there's a similar question: Automapper Mapping Multiple Properties to Single Property
Configuration:
CreateMap<C, A>()
.ForMember(o => o.Details, b => b.MapFrom<DetailsValueResolver>())
.ReverseMap();
Implementation:
// Note: this does not cover ReverseMap() when you would try to convert A to C
public class DetailsValueResolver : IValueResolver<C, A, B>
{
// Runs every time you map C to A
public B Resolve(C source, A destination, B destMember, ResolutionContext context)
{
// Covers cases where you can get null or empty DetailName, as well as null or zero DetailId
if (!string.IsNullOrEmpty(source.DetailName) && source.DetailId > 0)
{
return new B { Id = (int)source.DetailId, Name = source.DetailName };
}
return null;
}
}
You also can omit explicitly setting strings and classes as nullable types with ? as you do here:
public B? Details { get; set; }
public string? DetailName { get; set; }
Because string type and any class is null by default.

C# - Automapper - Map element with condition

I have two classes:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Quantity { get; set; }
};
public class Customer_
{
public string FirstNam { get; set; }
public string LastNam { get; set; }
public int Quantity { get; set; }
}
And a mapping between the two with Automapper library :
Customer[] data = new Customer[3];
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, Customer_>()
.ForMember(dest => dest.FirstNam, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.LastNam, opt => opt.MapFrom(src => src.LastName));
});
IMapper mapper = config.CreateMapper();
Customer_[] desti = mapper.Map<Customer[], Customer_[]>(data);
It works, but how to map only the elements of my array data that meet a condition ? For example : map only the element who have a Quantity > x
Thank's
Typically I'd do this before you map into AutoMapper:
Customer_[] desti = mapper.Map<Customer[], Customer_[]>(
data.Where(c => c.Quantity > 10).ToArray());
Don't try to put this sort of intelligence into AutoMapper, it's confusing and will lead to bugs.

Mapping a single child

The mapping below works, but I was wondering if it can be done with less configuration. I've tried playing around with ForAllMembers and ForSourceMember but I haven't found anything that works so far.
Classes
public class User
{
[Key]
public int ID { get; set; }
public string LoginName { get; set; }
public int Group { get; set; }
...
}
public class UserForAuthorisation
{
public string LoginName { get; set; }
public int Group { get; set; }
}
public class Session
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public virtual User User { get; set; }
...
}
Configuration
Mapper.CreateMap<Session, UserForAuthorisation>()
.ForMember(u => u.LoginName, m => m.MapFrom(s => s.User.LoginName))
.ForMember(u => u.Group, m => m.MapFrom(s => s.User.Group));
Query
UserForAuthorisation user = this.DbContext.Sessions
.Where(item =>
item.ID == SessionID
)
.Project().To<UserForAuthorisation>()
.Single();
Edit This works for the reverse.
Mapper.CreateMap<UserForAuthorisation, User>();
Mapper.CreateMap<UserForAuthorisation, Session>()
.ForMember(s => s.User, m => m.MapFrom(u => u));
var source = new UserForAuthorisation()
{
Group = 5,
LoginName = "foo"
};
var destination = Mapper.Map<Session>(source);
Unfortunately, Reverse() isn't the easy solution, mapping doesn't work.
Mapper.CreateMap<UserForAuthorisation, User>().ReverseMap();
Mapper.CreateMap<UserForAuthorisation, Session>()
.ForMember(s => s.User, m => m.MapFrom(u => u)).ReverseMap();
var source = new Session()
{
User = new User()
{
Group = 5,
LoginName = "foo"
}
};
var destination = Mapper.Map<UserForAuthorisation>(source);
I can see only one option to do less configurations. You can use benefit of flattering by renaming properties of UserForAuthorisation class to:
public class UserForAuthorisation
{
public string UserLoginName { get; set; }
public int UserGroup { get; set; }
}
In this case properties of nested User object will be mapped without any additional configuration:
Mapper.CreateMap<Session, UserForAuthorisation>();

Automapper not ignoring nested property

I've had a look through the various similar posts but can't spot the error of my ways with this one. Basically I have two views which update different parts of a "Settings" object. The view models contain one of two properties and depending on which is being set, should ignore the other. It works fine when it's ViewModel ==> Entity direct but when Automapper tries to update the nested object it fails.
I have the following object structure:
public class Account
{
public int AccountId { get; set; }
public DateTime DateToBeIgnored { get; set; }
public AccountSetting Settings { get; set; }
}
public class AccountSetting
{
public string PropertyOne { get; set; }
public string PropertyTwo { get; set; }
}
public class AccountViewModel
{
public int AccountId { get; set; }
public DateTime DateToBeIgnored { get; set; }
public AccountSettingViewModel Settings { get; set; }
}
public class AccountSettingViewModel
{
public string PropertyTwo { get; set; }
}
public class OtherAccountSettingViewModel
{
public string PropertyOne { get; set; }
}
With the mappings:
void WireUpMappings()
{
// From the entities to the view models
Mapper.CreateMap<Account, AccountViewModel>();
Mapper.CreateMap<AccountSetting, AccountSettingViewModel>();
Mapper.CreateMap<AccountSetting, OtherAccountSettingViewModel>();
// From the view models to the entities
Mapper.CreateMap<AccountViewModel, Account>()
.ForMember(dest => dest.DateToBeIgnored, opt => opt.Ignore());
Mapper.CreateMap<AccountSettingViewModel, AccountSetting>()
.ForMember(dest => dest.PropertyTwo, opt => opt.Ignore());
Mapper.CreateMap<OtherAccountSettingViewModel, AccountSetting>()
.ForMember(dest => dest.PropertyOne, opt => opt.Ignore());
Mapper.AssertConfigurationIsValid();
}
When mapping [OtherAccountSettingViewModel --> AccountSetting], only the property "PropertyTwo" is assigned (and the original value for "PropertyOne" remains unchanged) -this is what I would expect.
However when mapping [AccountViewModel --> Account] "DateToBeIgnored" is ignored as intended whereas Account.AccountSetting.PropertyTwo's previous value is replaced with "null".
Can anyone spot the error of my ways?
Here is the solution:
[TestMethod]
public void TestMethod1()
{
Mapper.CreateMap<AccountViewModel, Account>()
.ForMember(dest => dest.DateToBeIgnored, opt => opt.Ignore())
.ForMember(dest=>dest.Settings, opt=>opt.UseDestinationValue());
Mapper.CreateMap<AccountSettingViewModel, AccountSetting>()
.ForMember(dest=>dest.PropertyOne, opt=>opt.Ignore())
.ForMember(dest => dest.PropertyTwo, opt => opt.MapFrom(a => a.PropertyTwo));
Mapper.AssertConfigurationIsValid();
AccountViewModel viewmodel = new AccountViewModel()
{
AccountId = 3,
DateToBeIgnored = DateTime.Now,
Settings = new AccountSettingViewModel() { PropertyTwo = "AccountSettingViewModelPropTwo" }
};
Account account = new Account()
{
AccountId = 10,
DateToBeIgnored = DateTime.Now,
Settings = new AccountSetting() { PropertyOne = "AccountPropOne", PropertyTwo = "AccountPropTwo" }
};
account = Mapper.Map<AccountViewModel, Account>(viewmodel, account);
Assert.IsNotNull(account);
}

Categories

Resources