I've got a source class like the following:
public class Source
{
public Field[] Fields { get; set; }
public Result[] Results { get; set; }
}
And have a destination class like:
public class Destination
{
public Value[] Values { get; set; }
}
So I want to map from EITHER Fields or Results to Values depending on which one is not null (only one will ever have a value).
I tried the following map:
CreateMap<Fields, Values>();
CreateMap<Results, Values>();
CreateMap<Source, Destination>()
.ForMember(d => d.Values, opt =>
{
opt.PreCondition(s => s.Fields != null);
opt.MapFrom(s => s.Fields });
})
.ForMember(d => d.Values, opt =>
{
opt.PreCondition(s => s.Results != null);
opt.MapFrom(s => s.Results);
});
Only issue with this is that it looks if the last .ForMember map doesn't meet the condition it wipes out the mapping result from the first map.
I also thought about doing it as a conditional operator:
opt => opt.MapFrom(s => s.Fields != null ? s.Fields : s.Results)
But obviously they are different types so don't compile.
How can I map to a single property from source properties of different types based on a condition?
Thanks
There is a ResolveUsing() method that allows you for more complex binding and you can use a IValueResolver or a Func. Something like this:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Values, mo => mo.ResolveUsing<ConditionalSourceValueResolver>());
And the value resolver depending on your needs may look like:
public class ConditionalSourceValueResolver : IValueResolver<Source, Destination, Value[]>
{
public Value[] Resolve(Source source, Destination destination, Value[] destMember, ResolutionContext context)
{
if (source.Fields == null)
return context.Mapper.Map<Value[]>(source.Results);
else
return context.Mapper.Map<Value[]>(source.Fields);
}
}
Following #animalito_maquina answer.
Here is an update for 8.0 Upgrade:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Values, mo => mo.MapFrom<ConditionalSourceValueResolver>());
And to save you time, ValueResolvers are not supported for Queryable Extensions
ResolveUsing is not available, try this.
It's working for me
CreateMap<Source, Destination>()
.ForMember(opt => opt.value, map =>
map.MapFrom((s, Ariel) => s.Fields != null ? s.Fields : s.Results));
Related
I need a little help with some mappings I am doing.
I am mapping a Model which has two fields
public ProductCategory
public string FirstType
public string SecondType
to another Model which has only one field
public string ProductType
Now I have to map the First or Second Type to ProductType based on a the content of ProductCategory.And if the condition is not met the ProductType should be null
For example I need something like this:
.ForMember(dest => dest.ProductType, opt => opt.MapFrom(src =>
{
if (src.ProductCategory.Equals("something")
{
src.FirstType
}
else if (src.ProductCategory.Equals("something")
{
src.SecondType
}
else
{
null
}
}))
Of course the syntax is completely wrong and obviously won`t work , I just wanted to explain what I am trying to achieve.
I have a temporary solution
.ForMember(dest => dest.ProductType, opt => opt.MapFrom(src => src.ProductCategory.Contains("something") ? src.FirstType: src.SecondType))
but it is not completely what I need.
Any suggestions?
Thanks in advance
What you can do to avoid making the map code look very tangled is to actually separate it into methods that you actually know require some checking for the right value to be assigned.
Here's the code
.ForMember(dest => dest.ProductType, opt => opt.MapFrom(src => CalculateProductCategory(src.ProductCategory))) and then you write your own CalculateProductCategory
And your method would look something like this
public ProductType CalculateProductCategory(ProductCategory category) {
if (productCategory.Equals("something")
{
return FirstType
}
else if (productCategory.Equals("something")
{
return SecondType
}
else
{
return null
}
}
I have two classes and map one to other with Automapper. For instance:
public class Source
{
// IdName is a simple class containing two fields: Id (int) and Name (string)
public IdName Type { get; set; }
public int TypeId {get; set; }
// another members
}
public class Destination
{
// IdNameDest is a simple class such as IdName
public IdNameDest Type { get; set; }
// another members
}
Then I use Automapper to map Source to Destination:
cfg.CreateMap<Source, Destination>();
It works properly but sometimes member Type in class Source becomes null. In these cases I would like to map member Type in class Destination from TypeId property. That's what I want in a nutshel:
if Source.Type != null
then map Destination.Type from it
else map it as
Destination.Type = new IdNameDest { Id = Source.Id }
Is it possible with AutoMapper?
You can use the .ForMember() method while declaring the mapping.
Like so :
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type != null ? src.Type : new IdNameDest { Id = src.Id }));
While LeeeonTMs answer works fine AutoMapper provides a specialised mechanism to substitute null values. It "allows you to supply an alternate value for a destination member if the source value is null anywhere along the member chain" (taken from the AutoMapper manual).
Example:
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Value, opt => opt.NullSubstitute(new IdNameDest { Id = src.Id }));
With C# 6.0, the null-coalescing operator can be used.
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type ?? new IdNameDest { Id = src.Id }));
I managed to resolve it with Mapping Resolvers
public class SomeResolver : IValueResolver<Soruce, Dest, Guid>
{
public Guid Resolve(Source source, Dest destination, Guid, destMember, ResolutionContext context)
{
destination.Value= source.Value!=null ? source.Value:0;
return destination.MainGuid = Guid.NewGuid();
}
}
and then on mapping configuraiton
CreateMap<BioTimeEmployeeSummaryDTO, BioTimeEmployeeAttendanceSummary>()
.ForMember(dest => dest.MainGuid, opt => opt.MapFrom<YourResolverClass>())
.ReverseMap();
Is there a way to pass the properties I want to retrieve from the DB in a Select dynamically, I don't know the properties I need beforehand and I don't want to write the conditions in my repository.
I don't want to retrieve all the fields at once, just the ones I need based on some conditions.
For example:
public class Student
{
public string Property1 {get; set;}
public string Property2 {get; set;}
//other properties here
[NotMapped]
public string Selected
{
if(condition)
return Property1;
else
return Property2;
}
}
and in the service layer I have
query.Select(s => new StudentViewModel
{
Value = s.Selected; //this one will get the property we want based on a condition
//other stuff here
//OtherValue = s.OtherProperty
}
).FirstOrDefault();
Selector
An easy but ugly way is to use a Selector:
query.Select(Selector()).FirstOrDefault();
And the Selector can look like this:
private static Expression<Func<Student, StudentViewModel>> Selector()
{
if (Condition())
return x => new StudentViewModel
{
Name = x.Property1,
OtherName = x.OtherName
};
return x => new StudentViewModel
{
Name = x.Property2,
OtherName = x.OtherName
};
}
As you can see the obviously downside here is that you need to copy/paste all other selected properties. That is why its ugly.
AutoMapper
Configs
You can use AutoMapper with different configurations. First you need to define a standard mapping for all properties that don't need to be dynamic.
public static void AddStandardStudentMap(this IMappingExpression<Student, StudentViewModel> map)
{
map.ForMember(dest => dest.OtherName, opt => opt.MapFrom(src => src.OtherProperty))
.ForMember(dest => dest.OtherName2, opt => opt.MapFrom(src => src.OtherProperty2));
// you can concat .ForMember() for each property you need.
}
Next, you need to define the different configs and add the AddStandardStudentMap method to each invidual mapping.
var config1 = new MapperConfiguration(cfg =>
cfg.CreateMap<Student, StudentViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Property1))
.AddStandardStudentMap()
);
var config2 = new MapperConfiguration(cfg =>
cfg.CreateMap<Student, StudentViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Property2))
.AddStandardStudentMap()
);
After this, just use your conditions to decide which config do you need
IConfigurationProvider provider;
if(Condition())
provider = config1;
else
provider = config2;
And then instead of .Select() use:
query.ProjectTo<StudentViewModel>(provider).FirstOrDefault();
As we can see this solution is still ugly and has a lot of overhead but its needed in some cases, thats why i stated it here.
Expression
This is a bit similar to the Configs but brings you more flexibility and less writing effort.
First create a config but this time with a Selector
var config = new MapperConfiguration(cfg =>
cfg.CreateMap<Student, StudentViewModel>()
.ForMember(dest => dest.OtherName, opt => opt.MapFrom(src => src.OtherName))
.ForMember(dest => dest.OtherName2, opt => opt.MapFrom(src => src.OtherName2))
// and so on. Map all your properties that are not dynamically.
// and then the Selector
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => Selector()))
);
The Selector method can look like this:
private static Expression<Func<Student, StudentViewModel>> Selector()
{
if(Condition())
return src => src.Property1;
else
return src => src.Property2;
}
And then just use it like the configs solution but without selecting a particular config:
query.ProjectTo<StudentViewModel>(config).FirstOrDefault();
Conclusion
I know this is a lot input and there are even more possibilities to achieve the behaviour that you want, with or without AutoMapper. But i would suggest you (grounded on the information you gave us) to use AutoMapper with Expressions. It should give the flexibility you need and is extensible for further requirements.
I'm having some trouble getting this to work correctly. I have two classes:
public class TestClassA
{
public int? NullableIntProperty { get; set; }
}
public class TestClassB
{
public int NotNullableIntProperty { get; set; }
}
I then set up the following mappings:
cfg.CreateMap<TestClassA, TestClassB>()
.ForMember(dest => dest.NotNullableIntProperty,
opt => opt.MapFrom(src => src.NullableIntProperty));
cfg.CreateMap<TestClassA, TestClassA>()
.ForMember(dest => dest.NullableIntProperty,
opt => opt.MapFrom(src => src.NullableIntProperty));
cfg.CreateMap<TestClassB, TestClassA>()
.ForMember(dest => dest.NullableIntProperty,
opt => opt.MapFrom(src => src.NotNullableIntProperty));
cfg.CreateMap<TestClassB, TestClassB>()
.ForMember(dest => dest.NotNullableIntProperty,
opt => opt.MapFrom(src => src.NotNullableIntProperty));
I now have four mappings set up, and will test the following scenarios:
int? => int
int => int?
int => int
int? => int?
In a test class, I then use the mappings like this:
var testQueryableDest = testQueryableSrc.ProjectTo<...>(_mapper.ConfigurationProvider);
The only projection I would expect not to work at this stage would be TestClassA => TestClassB, since I can see how AutoMapper may not know what to do with the int? in cases where the value is null. Sure enough, that's exactly the case. So I set up a mapping for int? => int like so:
cfg.CreateMap<int?, int>()
.ProjectUsing(src => src ?? default(int));
This is where things become strange. As soon as I add this mapping, the mapping from TestClassB => TestClassB fails to even create. It gives this error message:
Expression of type 'System.Int32' cannot be used for assignment to type 'System.Nullable`1[System.Int32]'
I find this message incredibly strange as TestClassB does not have an int? on it at all. So what's going on here? I feel like I must be misunderstanding something about how AutoMapper needs these projections to be handled. I realise the various bits of code may be tricky to piece together so here's the entire test class for reference:
[TestClass]
public class BasicTests
{
private readonly IMapper _mapper;
public BasicTests()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<int?, int>()
.ProjectUsing(src => src ?? default(int));
cfg.CreateMap<TestClassA, TestClassB>()
.ForMember(dest => dest.IntProperty, opt => opt.MapFrom(src => src.NullableIntProperty));
cfg.CreateMap<TestClassA, TestClassA>()
.ForMember(dest => dest.NullableIntProperty, opt => opt.MapFrom(src => src.NullableIntProperty));
cfg.CreateMap<TestClassB, TestClassA>()
.ForMember(dest => dest.NullableIntProperty, opt => opt.MapFrom(src => src.IntProperty));
cfg.CreateMap<TestClassB, TestClassB>()
.ForMember(dest => dest.IntProperty, opt => opt.MapFrom(src => src.IntProperty));
});
_mapper = new Mapper(config);
}
[TestMethod]
public void CanMapNullableIntToInt()
{
var testQueryableSource = new List<TestClassA>
{
new TestClassA
{
NullableIntProperty = null
}
}.AsQueryable();
var testQueryableDestination = testQueryableSource.ProjectTo<TestClassB>(_mapper.ConfigurationProvider);
}
[TestMethod]
public void CanMapNullableIntToNullableInt()
{
var testQueryableSource = new List<TestClassA>
{
new TestClassA
{
NullableIntProperty = null
}
}.AsQueryable();
var testQueryableDestination = testQueryableSource.ProjectTo<TestClassA>(_mapper.ConfigurationProvider);
}
[TestMethod]
public void CanMapIntToNullableInt()
{
var testQueryableSource = new List<TestClassB>
{
new TestClassB
{
IntProperty = 0
}
}.AsQueryable();
var testQueryableDestination = testQueryableSource.ProjectTo<TestClassA>(_mapper.ConfigurationProvider);
}
[TestMethod]
public void CanMapIntToInt()
{
var testQueryableSource = new List<TestClassB>
{
new TestClassB
{
IntProperty = 0
}
}.AsQueryable();
var testQueryableDestination = testQueryableSource.ProjectTo<TestClassB>(_mapper.ConfigurationProvider);
}
}
I’ve found the shortest way to reproduce this situation is the following:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<int?, int>().ProjectUsing(x => x ?? default(int));
cfg.CreateMap<TestClassA, TestClassA>()
.ForMember(a => a.NullableIntPropety, o => o.MapFrom(a => a.NullableIntProperty));
}
It seems to me that AutoMapper is attempting to use the int? => int mapper here although a more obvious identity-based mapping would be to use here.
Since every int is also a valid int?, AutoMapper attempts to use the int? => int mapper here and assign the result to the int? member. But it seems that under the hood something does not correctly work when resolving just that assignment, hence that exception.
What seems to fix it is to add another mapping, an identity mapping for int? => int?:
cfg.CreateMap<int?, int?>().ProjectUsing(x => x);
Then, this mapping is being used instead and no exception occurs (and the mapping also properly works—with all of your examples).
This problem seems to exist on the current AutoMapper 5.1.x release (current is 5.1.1). The good news is, that it has already been fixed. If you try the current 5.2 alpha from the myget feed, then the code works fine without any issues.
Since the 5.1.1 release, the code base has seen quite a few contributions with multiple fixes on nullable mapping (e.g. this and this pull request). I assume that one of those changes has fixed this problem.
Most likely, it was pull request #1672 which just meant to remove unneeded code but apparently also fixed issue 1664 which was about AutoMapper apparently prioritizing nullable source mappings over non-nullable sources even if a non-nullable source was being mapped. And that sounds very much like this very problem you have experienced.
So, for now, you can add above workaround to map the type to itself or use an alpha release, while we wait for 5.2 to be released.
Is it possible to ignore mapping a member depending on the value of a source property?
For example if we have:
public class Car
{
public int Id { get; set; }
public string Code { get; set; }
}
public class CarViewModel
{
public int Id { get; set; }
public string Code { get; set; }
}
I'm looking for something like
Mapper.CreateMap<CarViewModel, Car>()
.ForMember(dest => dest.Code,
opt => opt.Ignore().If(source => source.Id == 0))
So far the only solution I have is too use two different view models and create different mappings for each one.
The Ignore() feature is strictly for members you never map, as these members are also skipped in configuration validation. I checked a couple of options, but it doesn't look like things like a custom value resolver will do the trick.
Use the Condition() feature to map the member when the condition is true:
Mapper.CreateMap<CarViewModel, Car>()
.ForMember(dest => dest.Code, opt => opt.Condition(source => source.Id != 0))
I ran into a similar issue, and while this will overwrite the existing value for dest.Code with null, it might be helpful as a starting point:
AutoMapper.Mapper.CreateMap().ForMember(dest => dest.Code,config => config.MapFrom(source => source.Id != 0 ? null : source.Code));
Here is the documentation of the conditional mapping:
http://docs.automapper.org/en/latest/Conditional-mapping.html
There's also another method called PreCondition very useful on certain scenarios since it runs before the source value is resolved in the mapping process:
Mapper.PreCondition<CarViewModel, Car>()
.ForMember(dest => dest.Code, opt => opt.Condition(source => source.Id == 0))