I need to trace any complex (i.e. non-default) mappings in our project.
To achieve this, I'm using a custom value resolver, and publishing out a log event during resolution. As part of this message I'd like to know the destination member being mapped, which I was hoping to find in source.Context.MemberName - but this is always null.
ValueResolver:
public class Resolver : IValueResolver
{
public event MappingEventHandler MappingEvent;
public delegate void MappingEventHandler(MappingMessage m);
public ResolutionResult Resolve(ResolutionResult source)
{
var src = (SourceDTO)source.Context.SourceValue;
if (!String.IsNullOrWhiteSpace(src.Status) && src.Status == "Alert")
{
var newValue = source.Value + " - Fail";
var fieldName = source.Context.MemberName; //Always null
MappingEvent(new MappingMessage(fieldName , newValue));
return source.New(value, typeof(String));
}
return source;
}
}
... and its usage:
Resolver resolver = new Resolver();
//... subscribe to resolver events etc.
Mapper.CreateMap<SourceDTO, Common>()
.ForMember(dest => dest.ReferenceIdentifier
, opt => opt.ResolveUsing<Resolver>()
.FromMember(src => src.Reference)
.ConstructedBy(() => resolver)
I can see in the Automapper code that MemberName only returns if the PropertyMap is non-null, and since PropertyMap is null in this case, I'm not getting my MemberName back.
Is there a reason the PropertyMap isn't being defined in this here? There's a relevant candidate via source.Context.TypeMap.GetPropertyMaps(), but it's not being pushed into this context.
Any ideas? Perhaps there's a means of pulling the right PropertyMap out of the Context.TypeMap set?
Tried with the more recent Automapper build - looks like the problem has been resolved.
Version with issue: 2.1.266
Working version: 2.2.1
Also found it's a lot easier to use the following syntax to resolve from an existing instance:
Resolver resolver = new Resolver();
//... subscribe to resolver events etc.
Mapper.CreateMap<SourceDTO, Common>()
.ForMember(dest => dest.ReferenceIdentifier
, opt => opt.ResolveUsing(resolver)
.FromMember(src => src.Reference) )
Related
I'm having some issues mapping two classes using the AutoMapper in version 11. The destination class has an indexer-property, which causes the issue.
Since Automapper 11, the indexer property is no longer automatically ignored.
For testing purposes I used three classes:
public class Source {}
public class Target {
public float this[int key]
{
get
{
return 0;
}
set
{
}
}
}
public class MapperProfile: Profile
{
public MapperProfile()
{
CreateMap<Source, Target>();
}
}
During startup I'm calling mapper.ConfigurationProvider.AssertConfigurationIsValid() to validate the configuration. This fails with an unmapped Item property.
While it is possible to ignore all properties starting with Item using
this.AddGlobalIgnore("Item")
inside the Profile, I'd rather not use such a general way to ignore it, especially since the first parameter is labeled propertyNameStartingWith - this would suggest to me, that other properties such as ItemWithSuffix might be ignored as well.
Another strategy I tried to employ is to use an explicit ignore on a property. Using the expression notation fails, due to compiler errors:
CreateMap<Source, Target>()
.ForMember(dest => dest[], opt => opt.Ignore())
.ReverseMap();
Adding an arbitrary index to the expression fails with another error, so that does not seem to be a viable solution as well:
CreateMap<Source, Target>()
.ForMember(dest => dest[0], opt => opt.Ignore())
.ReverseMap();
In this case the error notes, that we may not map to child property.
When using the member name syntax, there are some different errors.
CreateMap<Source, Target>()
.ForMember("Item", opt => opt.Ignore())
.ReverseMap();
In this case it fails with the following message:
Incorrect number of arguments supplied for call to method 'Double get_Item(Int32)' (Parameter 'property')
Using [] or Item[] fails with a missing property notification.
The last strategy I employed was using the ForAllMembers call. This succeeds, however, I'm wondering if there is a better solution to handle this logic which allows using a specific mapping logic for a single member.
CreateMap<Source, Target>()
.ForAllMembers(x =>
{
if (x.DestinationMember.Name == "Item")
{
x.Ignore();
}
});
I'm still trying to wrap my head around how automapper works. I have the EF Core query below, which I'd like to change to using automapper.
var query = from t in Context.Tririga.AsNoTracking()
let l = t.Building
let m = t.Owner
let o = m.Organization
where o.Active
select new MetricBadLabManagerByOrganizationDTO {
CampusName = l.CampusName,
Email = m.Email,
Name = m.Name,
OrgLevel3 = o.ThreeName,
OrgLevel4 = o.FourName,
OrgLevel5 = o.FiveName,
OrgLevel6 = o.SixName,
OrgLevel7 = o.SevenName,
Reason = m.Active == false ? "Inactive Employee" : "Invalid Employee",
SiteName = l.SiteName,
Wwid = m.Wwid
};
return await query.ToArrayAsync();
I'm not sure how to setup a mapper configuration to the DTO type because I can't just go from Tririga to MetricBadLabManagerByOrganizationDTO as it doesn't know how to go down the relationships.
Here is the Getting Started Guide from AutoMapper if you haven't gone through the documentation.
I've recently got a chance to work on a project that uses AutoMapper to translate between persistence models and domain models, and here would be how I set things up:
There are many ways to configure your mappings. I like the Profile Instances method:
using AutoMapper;
namespace Company.Product.Infrastructure.Mapping.AutoMapper
{
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Tririga, MetricBadLabManagerByOrganizationDTO>()
.ForMember(dest => dest.CampusName,
opts => opts.MapFrom(src => src.Building.CampusName))
.ForMember(dest => dest.Email,
opts => opts.MapFrom(src => src.Owner.Email))
.ForMember(dest => dest.Name,
opts => opts.MapFrom(src => src.Owner.Name))
...
}
}
}
There is a whole section about flattening on AutoMapper documentation.
Just a note,
When you configure a source/destination type pair in AutoMapper, the configurator attempts to match properties and methods on the source type to properties on the destination type. If for any property on the destination type a property, method, or a method prefixed with “Get” does not exist on the source type, AutoMapper splits the destination member name into individual words (by PascalCase conventions).
So you might not need to define the rule for each single property you want to map, otherwise what's the point of using AutoMapper.
For example, if your MetricBadLabManagerByOrganizationDTO campus name were named as BuildingCampusName, AutoMapper would be smart enough to look for Building property on your source and see if there is a property called CampusName inside.
There are just lots of valuable information in AutoMapper documentation you can find and learn from, which is what I like!
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)));
I'm trying to write a custom validator that will check if an entity exists in the database, using OrmLite. The problem is that the type arguments for IRuleBuilder can no longer be inferred from usage.
I have to write the method call like this:
RuleFor(r => r.Id).Exists<DtoName, int, EntityName>()
But I want to write it like this:
Rulefor(r => r.Id).Exists<EntityName>()
This happens because IRuleBuilder has two type parameters and the method is an extension method. Is there a smart, fluent way to design this and make the function call preferably like the second version?
Here is code for my extension method and my validator:
public static class AbstractValidatorExtensions
{
public static IRuleBuilderOptions<T, TProperty> Exists<T, TProperty, U>(this IRuleBuilder<T, TProperty> ruleBuilder)
{
return ruleBuilder.SetValidator(new EntityExistsValidator<U>());
}
}
public class EntityExistsValidator<T> : PropertyValidator
{
public EntityExistsValidator() : base("Entity does not exist") {}
protected override bool IsValid(PropertyValidatorContext context)
{
return HostContext.Resolve<Repository>()
.Exists<T>((int)context.PropertyValue);
}
}
My experience with FluentValidation is that you’re trying to push more and more logic into validators. I would not do this as it adds too much complexity. My rule of thumb is to validate discrete property values only. Example: I would just use FluentValidation to check if property int Id is 0 or greater than 0. The check if the entity already exists I would move to another service (often called “the business logic”).
You'll need to a Custom Validator for custom validation to access dependencies, something like:
RuleFor(x => x.Id)
.Must(id =>
{
using (var db = HostContext.AppHost.GetDbConnection(base.Request))
{
return !db.Exists<EntityName>(x => x.Id == id);
}
})
.WithErrorCode("AlreadyExists")
.WithMessage("...");
I'd also consider just doing validation that use dependencies in your Services instead:
if (Db.Exists<EntityName>(x => x.Id == request.Id))
throw new ArgumentException("Already Exists", nameof(request.Id));
I've configured AutoMapper to, I thought, use AutoFac to create object instances by defining the following AutoFac Module:
public class AutoMapperModule : Module
{
protected override void Load( ContainerBuilder builder )
{
base.Load( builder );
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
builder.RegisterAssemblyTypes( assemblies )
.Where( t => typeof(Profile).IsAssignableFrom( t ) && !t.IsAbstract && t.IsPublic )
.As<Profile>();
builder.Register( c => new MapperConfiguration( cfg =>
{
//cfg.ConstructServicesUsing( CSContainer.Instance.Resolve );
foreach( var profile in c.Resolve<IEnumerable<Profile>>() )
{
cfg.AddProfile( profile );
}
} ) )
.AsSelf()
.AutoActivate()
.SingleInstance();
builder.Register( c => c.Resolve<MapperConfiguration>().CreateMapper( c.Resolve ) )
.As<IMapper>()
.SingleInstance();
}
}
I then created AutoMapper Profiles such as the following:
public class CommunityScannerManagerAutoMapProfile : Profile
{
public CommunityScannerManagerAutoMapProfile()
{
CreateMap<CommunityUser, CommunityUserModel>()
//.ConstructUsing(src => new CommunityUserModel(CSContainer.Instance.Resolve<IValidationService>()))
.ForMember( dest => dest.VaultKey, opt => opt.MapFrom( src => src.VaultKeyName ) )
.ReverseMap();
CreateMap<Community, CommunityModel>()
//.ConstructUsing(src=>new CommunityModel(CSContainer.Instance.Resolve<IValidationService>( )))
.ReverseMap();
CreateMap<ScannerConfiguration, CommunitiesModel>()
.ForMember( dest => dest.Communities, opt => opt.MapFrom( src => src.Communities ) )
.ReverseMap();
}
}
The destination types form a hierarchy: CommunitiesModel contains a collection of CommunityModel objects, and each CommunityModel object contains a collection of CommunityUserModel objects. The collections are mapped thru the AutoMapper Profile I created.
Each destination type -- CommunitiesModel, CommunityModel, and CommunityUserModel -- has constructor parameters which are registered with AutoFac (they themselves are also registered with AutoFac). So I thought AutoMapper would be able to resolve the instances it needed, calling upon AutoFac to create them.
But that's not the case. Unless I include those two commented out lines in the AutoMapper Profile class -- the ones starting with ConstructUsing -- I get an exception, from AutoMapper, that it can only handle classes with constructors that have no parameters, or only optional ones. Which implies AutoMapper is not using AutoFac to resolve/create those instances, contrary to what I expected.
Interestingly, AutoMapper has no problem creating instances of CommunitiesModel, the top level object, even though it, too, only has a constructor containing a parameter. So AutoMapper is using AutoFac for top level objects, but not the child objects.
Is there a global configuration I can set in AutoMapper to tell it to always use AutoFac? I tried using ConstructServicesUsing in configuring AutoMapper (the line is commented out in the code here), but it didn't seem to have any effect.
Update
Apparently, AutoMapper doesn't use DI to create objects "internally" (see #Lucian's comment).
But I noticed AutoMapper does support a ConstructUsing() clause, which allows you to tie it into a DI container like AutoMap:
CreateMap<CommunityUser, CommunityUserModel>()
.ConstructUsing(src => CSContainer.Instance.Resolve<CommunityUserModel>())
.ForMember( dest => dest.VaultKey, opt => opt.MapFrom( src => src.VaultKeyName ) )
.ReverseMap();
So a related/follow-on question is: is there a way to make this the behavior without having to add the line to every CreateMapper() call where it applies?