Specify a conversion for an interface with automapper - automapper

Most of my EF objects have a TenantId Property. The system never have to handle the tenantId it's all taken care for before the SaveChanges(). I want to write an automapper map that will always ignore the TenantId Field.
I've tried
Mapper.CreateMap<IDomainObject, ITenantData>()
.ForMember(m => m.TenantId, a => a.Ignore());

You can handle it using mapping inheritance. For instance:
Mapper.CreateMap<IDomainObject, ITenantData>()
.Include<DomainObject1, TenantData1>()
.ForMember(m => m.TenantId, a => a.Ignore());

Related

Using string.Split() in AutoMapper issue

I have an ASP .Net core application. I am simply trying to have my AutoMapper configure to convert a string comma delimited into a list of strings as per this configuration:
configuration.CreateMap<Job, JobDto>()
.ForMember(dto => dto.Keywords, options => options.MapFrom(entity => entity.Keywords.Split(',').ToList()))
For some reason it does not get compiled and give me the following error:
An expression tree may not contain a call or invocation that uses
optional argument
I can't see why I am getting this error. I am pretty sure that I have done that in my other projects before without any such error.
As error says, Split function has an optional parameter. The full signature of it is as this (options is optional)
public string[] Split(string separator, StringSplitOptions options = StringSplitOptions.None)
As you are trying to use a function with default value inside an expression tree, it gives you the error.
To Fix it, easy, just pass on optional parameters by yourself. ( StringSplitOptions.None )
So, simply change it to this:
entity.Keywords.Split(',' , StringSplitOptions.None).ToList()
This is completely true.
Error is raised because expression tree being created is about to contain some more complex logic, like .Split(',').ToList(), which is not an accessible property or method, only top-level reflected object properties and methods are supported (like in class MemberInfo).
Property chaining, deep-calls (.obj1property.obj2property), extension methods are not supported by the expression trees, like in this .ToList() call.
My solution was like this:
// Execute a custom function to the source and/or destination types after member mapping
configuration.CreateMap<Job, JobDto>()
.AfterMap((dto,jobDto)=>jobDto.Keywords = dto.Keywords.Split(',').ToList());
I had the same problem. I do not know if it is an issue or not. Anyway, I found a workaround.
CreateMap<Category, GetCategoryRest>()
.ForMember(dest => dest.Words,
opt => opt.MapFrom(src => ToWordsList(src.Words)));
private static List<string> ToWordsList(string words)
{
return string.IsNullOrWhiteSpace(words) ? new List<string>() : words.Split(",").ToList();
}
It is guaranteed that AutoMapper has always a List. Still, I'm confused. In my Startup.cs I define that AutoMapper allows null values for list.
Mapper.Initialize(cfg => {
cfg.AllowNullCollections = true;
}
Category.Words is a string.
GetCategoryRest.Words is a List<string>
AutoMapper Version: 8.1.1,
AutoMapper.Microsoft.DependencyInjection: 6.1.1
Use .AfterMap
CreateMap<src, dto>()
.ForMember(src =>src.Categories,options=> options.Ignore())
.AfterMap((src, dto) => { dto.Categories.AddRange(src.Categories.Split(",").ToList()); })
.ReverseMap()
.ForMember(src => src.Categories, option => option.MapFrom(dto => string.Join(",", dto.Categories)));

How to relate two unrelated foreign keys back to the same entity

Using EF code first, I created compound primary key on Packages and Services consisting of Id and Member_Id.
Then I made foreign keys from both of those tables into Promises.
As you can see it didn't relate the two foreign keys back to the same Member thus allowing the package member to be different than the service member. This is not what I intended.
I intend to have Promises just have a single Member_Id column and reuse it in both foreign keys. In problem terms, I intend for promises to only have services of the member that owns that promise's package.
I can easily do this in the database. See this fixed diagram:
This features compound foreign keys from Promises back to Packages and Services.
How can I attribute my classes in EF to achieve this result?
Is my only option to Database First migration and check and see what it does to mimic what I did manually in the database diagram editor?
I had to:
Fix my database the way I wanted it
Create a new Class Libray Project
Add Entity Framework Model. Choose Code First from Existing Database
Look at the differences between my classes and the generated classes from Database First, and see which ones I need to apply to my model. In this case, I found that the only way to create the foreign keys I wanted was with code not attributes and this is done in the OnModelCreating routine in the model (DbContext) class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Member>()
.HasMany(e => e.Packages)
.WithRequired(e => e.Member)
.HasForeignKey(e => e.Member_Id);
modelBuilder.Entity<Member>()
.HasMany(e => e.Products)
.WithRequired(e => e.Member)
.HasForeignKey(e => e.Member_Id);
modelBuilder.Entity<Member>()
.HasMany(e => e.Services)
.WithRequired(e => e.Member)
.HasForeignKey(e => e.Member_Id);
modelBuilder.Entity<Package>()
.HasMany(e => e.Promises)
.WithRequired(e => e.Package)
.HasForeignKey(e => new { e.Package_Id, e.Member_Id })
.WillCascadeOnDelete(false);
modelBuilder.Entity<Service>()
.HasMany(e => e.Promises)
.WithRequired(e => e.Service)
.HasForeignKey(e => new { e.Service_Id, e.Member_Id })
.WillCascadeOnDelete(false);
}

Automapper ResolveUsing or MapFrom

I have a mapping definition defined as
Mapper.CreateMap<Calculator, CalculatorViewModel>()
.ForMember(dest => dest.TypeIndicator, src => src.ResolveUsing(new TypeIndicatorResolver()));
Should I be using ResolveUsing or MapFrom(src => SomePrivateMethod()) ?
What is the difference between ResolveUsing and MapFrom when it comes to complex mapping.
The Resolver or Private method will go to the database and get a value.
MapFrom uses Expressions, while ResolveUsing uses a Func. MapFrom only allows redirection of properties:
ForMember(d => d.Foo, opt => opt.MapFrom(src => src.Bar.Baz.Foo))
ResolveUsing can be anything
ForMember(d => d.Foo, opt => opt.ResolveUsing(src => HitDatabaseWithStuff(src));
I'd use a Resolver class when the resolution logic needs to be shared amongst more than one member, or if I want to have the resolver instantiated by a service locator. Otherwise, a private method is fine.

Must resolve to top-level member

I tried the following map:
CreateMap<ThemeNewModel, CreateThemeOrder.ThemeModel>()
.ForMember(d => d.Subject.Id, o => o.MapFrom(s => s.Subject));
Both Subject.Id and Subject are of type int. However, I get the following error:
Expression 'd => Convert(d.Subject.Id)' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.Parameter name: lambdaExpression
I am using AutoMapper 2.0. Can't I solve this without AfterMap?
What is the type of ThemeNewModel.Subject? Assuming its ThemeSubject, you may have success with something like:
CreateMap<ThemeSubject, CreateThemeOrder.ThemeModel>()
.ForMember(d=>d.Id, o => o.MapFrom(s->s.Subject));
CreateMap<ThemeNewModel, CreateThemeOrder.ThemeModel>()
.ForMember(d=>d.Subject, o => o.MapFrom(s => s);
If the above does not work, you should follow the advise in the exception, and create a custom resolver.
Anyway, automapper is designed to flatten from complex types to a flat model/viewmodel types, so your ThemeNewModel is too complex, and maybe you need to rethink your design.

AutoMapper Resolve a property value on the destination based on the property value of a third object

Is it possible to use AutoMapper to map from Source to Destination conditionally resolving some properties based on the property value of another object? For example, mapping Source.Property to Destination.Property where ThirdObject.CountryCode.Equals("SomeCountry").
The current code base is setup so that values are being mapped from a DataReader to a list of objects. Then, if the ThirdObject.CountryCode has a certain value, then an amount property on the destination object must be multiplied by a multiplier.
Currently, I'm thinking of solving the problem by coming up with something like:
Mapper.Map<IDataReader, Destination>(dataReader)
.OnCondition(ThirdObject.CountryCode.Equals("SomeCountry")
.ForMember(destination => destination.Amount)
.UpdateUsing(new Multiplier(fixedAmount));
I'm hoping there is an easier way before going down that path.
Look at ResolveUsing:
Mapper.CreateMap<Journal_Table, Journal>()
.ForMember(dto => dto.Id, opt => opt.MapFrom(src => src.JournalId))
.ForMember(dto => dto.Level, opt => opt.ResolveUsing<JournalLevelResolver>().FromMember(name => name.Journal_level));
Then:
public class JournalLevelResolver : ValueResolver<string, JournalLevel>
{
protected override JournalLevel ResolveCore(string level)
{
...

Resources