Automapper: How to flatten complex object to plain object - c#

public class Complex
{
public A A { get; set; }
public A B { get; set; }
}
public class A
{
public int a1 { get; set; }
public int a2 { get; set; }
}
public class B
{
public int b1 { get; set; }
public int b2 { get; set; }
}
//----------------Source Object End Here---------------------
public class Simple <----[This Simple class has only properties of A class]
{
public int aa1 { get; set; }
public int aa2 { get; set; }
}
//----------------Destination Object End Here---------------------
CreateMap<A, Simple>()
.ForMember(dest => dest.aa1, opt => opt.MapFrom(src => src.a1))
.ForMember(dest => dest.aa2, opt => opt.MapFrom(src => src.a2))
// Mapper IS NOT AVAILABLE HERE AS I AM USING PROFILE BASED CONFIGURATION
CreateMap<Complex, Simple>()
.ConvertUsing(src => Mapper.Map<A, Simple>(src.A)); <------Error at this line
//----------------Automammer config End Here---------------------
How to flatten from Complex to Simple? I don't wish to map Complex.A to Simple one by one again in the Complex to Simple config as it is already configured above.

Finally, I figured out with another overloaded method of ConvertUsing
CreateMap<Complex, Simple>()
.ConvertUsing((src,ctx) => {
return ctx.Mapper.Map<Complex, Simple>(src.A)
});
I feel this overloaded method has quite a multiple possibilities and flexibility. I don't have further issue of accessing Mapper directly as mentioned in the question. This overloaded method has its own context parameter (ResolutionContext). We can use Mapper from this context parameter like ctx.Mapper.Map<Complex, Simple>(src.A)

Related

Unable to map from one ViewModel to another

I'm making a list of checkboxes to update a user's roles, and I'm trying to map from this:
public class ApplicationRoleViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string NormalizedName { get; set; }
public string ConcurrencyStamp { get; set; }
public int SortOrder { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string Icon { get; set; } // Font Awesome-ikoner, f.eks. "fa-user"
}
to this:
public class SelectableRoleViewModel
{
public Guid Id { get; set; }
public string DisplayName { get; set; }
public bool Selected { get; set; }
}
This is my mapping:
CreateMap<ApplicationRoleViewModel, SelectableRoleViewModel>()
.ForMember(dest => dest.Id, s => s.MapFrom(i => i.Id))
.ForMember(dest => dest.DisplayName, s => s.MapFrom(d => d.DisplayName))
.ForMember(dest => dest.Selected, i => i.Ignore());
Mapping it like this in the controller:
ApplicationRole role = await db.Roles.FirstOrDefaultAsync();
SelectableRoleViewModel sr = auto.Map<SelectableRoleViewModel>(role);
gives me the following error message:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
I am registering AutoMapper in Startup.cs like this:
services.AddAutoMapper(typeof(Startup));
Then, in AutoMapperProfile.cs:
public class AutomapperProfile : Profile
{
public AutomapperProfile()
{
// This is not working:
CreateMap<ApplicationRoleViewModel, SelectableRoleViewModel>()
.ForMember(dest => dest.Selected, i => i.Ignore());
// This is working:
CreateMap<ApplicationUser, ApplicationUserViewModel>();
// Many more mappings, all working
}
}
How can I get it to work?
The code you specified seems to be correct.
I will just suggest to remove the ForMember method for properties with the same names as auto mapper handles it automatically:
CreateMap<ApplicationRoleViewModel, SelectableRoleViewModel>()
.ForMember(dest => dest.Selected, i => i.Ignore());
The problem seems to be because you are not using the mapper right. Where have you registered the mapper? Is the registration happens before the map? Did you do it in the Startup? If you specify more code, it will be easier to help.
UPDATE:
After getting more code & info, the problem was that the map worked on a different object, ApplicationRoleViewModel and not ApplicationRole.
Just to see a difference ;)
public static SelectableRoleViewModel ToSelectable(this ApplicationRoleViewModel model)
{
return new SelectableRoleViewModel
{
Id = model.Id,
DisplayName = model.DisplayName
};
}
// Usage
var selectable = applicationRole.ToSelectable();
Type it once
Perfectly testable
Fully maintainable - supports all kinds of conversion/mapping
Reduce amount of injected dependencies and abstractions (mapper)
No extra dependencies on third party libraries

How to force AutoMapper to be very restrict?

Here is an example:
class Source
{
public int Value_1 { get; set; }
public int Value_2 { get; set; }
public int MissedValue { get; set; }
}
class Destination
{
public int Value_1 { get; set; }
public int Value_2 { get; set; }
}
void Main()
{
var config = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Destination>()
.ForMember(dst => dst.Value_1, m => m.MapFrom(src => src.Value_1))
.ForMember(dst => dst.Value_2, m => m.MapFrom(src => src.Value_2))
;
});
config.AssertConfigurationIsValid();
}
In the end, i need AssertConfigurationIsValid() to throw an exception when i have missing Source fields and not just destination fields.
Also, how to force it to disable any type and naming conversions? Like int -> string, Test.Id -> TestId, etc.
I need it to be as restrict as possible. Or maybe there is a better alternative for my case?
UPDATE_0:
Ok. Found an answer to the first part.
Just need to use
cfg.CreateMap<Source, Destination>(AutoMapper.MemberList.Source)
It would be great to have support for both validation types alltogether, but it's a good start already.
Now what left is, how to remove all naming conventions and type converters.

Automapper before/after map callbacks during mapping

I'm using ASP.NET Core and Automapper 6.1.0 ,
I have two types that look like this
public class ExampleDTO
{
public Guid Id { get; set; }
public ProviderDTO Provider { get; set; }
}
public class Example
{
public Guid Id { get; set; }
public Guid Provider { get; set; }
}
ProviderDTO class (which is irelevant in this case)
public class ProviderDTO
{
public Guid Id { get; set; }
public string Name { get; set; }
}
AutoMapper configuration looks like this:
CreateMap<Example, ExampleDTO>().ForMember(x => x.Provider, opt => opt.Ignore());
CreateMap<ExampleDTO, Example>().ForMember(dest => dest.Provider,
opt => opt.MapFrom(src => src.Provider.Id));
When I map from Example to ExampleDTO, I want to pass the value for ProviderDTO type.
I tried something like this.
_mapper.Map<ExampleDTO>(example, opt => opt.AfterMap((src, dest) => dest.Provider = myProvider));
I get this
'object' does not contain a defenition for 'Provider' and no extension method
Is this achievable? If yes, what am I doing wrong?
With the AutoMapper, you may need to provide both the source and destination type, such as:
_mapper.Map<Example, ExampleDTO>(example, opt => {
opt.AfterMap((src, dest) => dest.Provider = myProvider))
});

Automapper projection and union

I have a problem with union and automapper projections.
I have two entities:
public class Entity
{
public DateTime ActionDate { get; set; }
public int SomeProp { get; set; }
}
public class ExtendedEntity
{
public DateTime ActionDate { get; set; }
public int SomeProp { get; set; }
public int SomeOtherProp { get; set; }
}
and projection:
public class EntityProjection
{
public DateTime ActionDate { get; set; }
public int SomeProp { get; set; }
public int SomeOtherProp { get; set; }
public string Source { get; set; }
}
i map entities to one projection, Entity does not have SomeOtherProp so i set 0 to it:
public class EntityProfile : Profile
{
protected override void Configure()
{
CreateMap<ExtendedEntity, EntityProjection>()
.ForMember(dest => dest.Source, opt => opt.MapFrom(src => "ext entity"));
CreateMap<Entity, EntityProjection>()
.ForMember(dest => dest.SomeOtherProp, opt => opt.MapFrom(src => 0))
.ForMember(dest => dest.Source, opt => opt.MapFrom(src => "entity"));
}
}
when i try to use next code i get error:
var entities = context.Set<Entity>()
.Project().To<EntityProjection>();
var extEntities = context.Set<ExtendedEntity>()
.Project().To<EntityProjection>();
var result = entities.Union(extEntities).OrderBy(p => p.ActionDate).ToList();
Error text: The type 'UserQuery+EntityProjection' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be...
That means that properties in projection must be initialized in same order, how i can set projection properties initialization order by automapper?
Very late answer, and the short version seems to be "You can't".
I had exactly the same question (Can I force Automapper to initialise properties in a certain order?) and ended up mapping everything within a LINQ select statement.
For ease, I made it a static method within my DTO (cut-down code):
public static IQueryable<MyDto> QueryableFromTaskType1(
IQueryable<TaskType1> query)
{
return query.Select(src => new MyDto()
{
TaskId = src.Id,
AssetTypeName = src.Asset.AssetType.Name,
AssetId = src.Asset.Id,
AssetCode = src.Asset.Code,
AssetName = src.Asset.Name,
});
}
public static IQueryable<MyDto> QueryableFromTaskType2(
IQueryable<TaskType2> query)
{
return query.Select(src => new MyDto()
{
TaskId = src.Id,
AssetTypeName = src.AssetTypeName,
AssetId = src.AssetId,
AssetCode = src.AssetCode,
AssetName = src.AssetName,
});
}
then you can get your objects, as an IQueryable, simply pass them through the appropriate static method (which appends a select into the DTO - or projects as it's otherwise known) and then Union or Concat the resulting IQueryables.
The only downside is that Automapper will normally deal with recursive automapping, although I'm pretty certain that wouldn't map to SQL well anyway, so you probably don't lose much.

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