How to force AutoMapper to be very restrict? - c#

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.

Related

Automapper: How to flatten complex object to plain object

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)

AutoMapper: What is the difference between ForMember() and ForPath()?

I am reading AutoMapper's ReverseMap() and I can not understand the difference between ForMember() and ForPath(). Implementations was described here. In my experience I achieved with ForMember().
See the following code where I have configured reverse mapping:
public class Customer
{
public string Surname { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class CustomerDto
{
public string CustomerName { get; set; }
public int Age { get; set; }
}
static void Main(string[] args)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Customer, CustomerDto>()
.ForMember(dist => dist.CustomerName, opt => opt.MapFrom(src => $"{src.Surname} {src.Name}"))
.ReverseMap()
.ForMember(dist => dist.Surname, opt => opt.MapFrom(src => src.CustomerName.Split(' ')[0]))
.ForMember(dist => dist.Name, opt => opt.MapFrom(src => src.CustomerName.Split(' ')[1]));
});
// mapping Customer -> CustomerDto
//...
//
// mapping CustomerDto -> Customer
var customerDto = new CustomerDto
{
CustomerName = "Shakhabov Adam",
Age = 31
};
var newCustomer = Mapper.Map<CustomerDto, Customer>(customerDto);
}
It is working.
Question
Do ForMember and ForPath the same things or when should I use ForPath() over ForMember()?
In this case, to avoid inconsistencies, ForPath is translated internally to ForMember. Although what #IvanStoev says makes sense, another way to look at it is that ForPath is a subset of ForMember. Because you can do more things in ForMember. So when you have a member, use ForMember and when you have a path, use ForPath :)

"Store" objects during mapping with automapper

Let's say, we have two source types for mapping, and one destination:
class TSource
{
public float A { get; set; }
public float B { get; set; }
}
class TSupport
{
public float C { get; set; }
public float D { get; set; }
}
class TDestination
{
public float A { get; set; }
public float B { get; set; }
public float C { get; set; }
public float D { get; set; }
}
And instance of TSupport is obtained with some method, using TSource instance:
private TSupport GetSupportInstance(TSource source)
So, usually, I'd do this with double mapping:
Mapper.CreateMap<TSource, TDestination>();
Mapper.CreateMap<TSupport, TDestination>();
//...
var support = GetSupportInstance(source);
var destination = Mapper.Map<TSource, TDestination>(source);
Mapper.Map(support, destination);
This also may be done in single mapping using manual resolve:
Mapper.CreateMap<TSource, TDestination>()
.ForMember(desc => desc.C, opt => opt.ResolveUsing(src =>
{
var support = GetSupportInstance(src);
return support.C;
}))
.ForMember(desc => desc.D, opt => opt.ResolveUsing(src =>
{
var support = GetSupportInstance(src);
return support.D;
}));
As you can see, I'm using GetSupportInstance twice, for each property. So, finally, my question is - is it possible to obtain support in mapping once, store it somehow, and reuse it in mappings for different properties?
How about this:
Mapper.CreateMap<TSource, TDestination>()
.ForMember(d => d.C, x => x.Ignore())
.ForMember(d => d.D, x => x.Ignore())
.AfterMap((src, dst) => {
var support = GetSupportInstance(src);
dst.C = support.C;
dst.D = support.D;
});
I'd probably go about using a custom resolver that caches the instance you need. It would look something like this.
public class MySupportInstanceResolver : ValueResolver<TSource, TDestination>
{
private static readonly ISupportInstance _supportInstance;
[ImportingConstructor]
public MySupportInstanceResolver ()
{
_supportInstance=GetSupportInstance();
}
protected override string ResolveCore(Journal source)
{
return _supportInstance;
}
}
Then you could use it as:
cfg.CreateMap<TSource, Tdest>()
.ForMember(dest => dest.C,
opt => opt.ResolveUsing<MySupportInstanceResolver>().FromMember(name => name.C));
});

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.

Need help with AutoMapper

I have two classes that looks as follows:
public class Rule
{
public int Id { get; set; }
public RuleGroup RuleGroup { get; set; }
}
public class RuleGroup
{
public int Id { get; set; }
public List<Rule> RuleList { get; set; }
}
A RuleGroup has a list of rules. My AutoMapper settings are as follows:
Mapper.CreateMap<RuleRecord, FirstSolar.Mes.Core.Entities.Recipe.Rule>()
.ForMember(destination => destination.RuleGroup, source => source.Ignore())
.ForMember(destination => destination.Id, source => source.MapFrom(item => item.RuleId));
Mapper.CreateMap<IList<RuleRecord>, IList<FirstSolar.Mes.Core.Entities.Recipe.Rule>>();
Mapper.CreateMap<RuleGroupRecord, FirstSolar.Mes.Core.Entities.Recipe.RuleGroup>()
.ForMember(destination => destination.Id, source => source.MapFrom(item => item.RuleGroupId));
Mapper.CreateMap<IList<RuleGroupRecord>, IList<FirstSolar.Mes.Core.Entities.Recipe.RuleGroup>>();
When I attempt to map a RuleGroupRecord (LinqToSQL object) to RuleGroup (DTO), AutoMapper says I need to add a mapping for RuleGroup.RuleList. I'm wondering why because I defined how to map a single RuleRecord and a List.
If I have to, how would I do it?
Simply add (I hope I got the syntax right, but you should see what I'm hinting at):
.ForMember(destination => destination.RuleList, source => source.MapFrom(item => item.Rules));
to the second mapping. While you handled the general mapping for RuleRecord to Rule in the first mapping, you didn't tell automapper to map the specific property RuleGroup.RuleList.

Categories

Resources