I am at a loss as to how to use the new IValueResolver interface in the new version of AutoMapper. Perhaps I used them improperly in the previous versions of AutoMapper...
I have a lot of model classes, some of them are generated from several databases on several database servers, using sqlmetal.
Some of these classes has a string property, PublicationCode, which identifies which publication the subscription, or offer, or invoice, or whatever it is, belongs to.
The publication can exist in either of two systems (the old and the new system), hence I have a bool property on the destination model classes which tells whether the publication is in the old or the new system.
Using the old version (<5?) of AutoMapper, I used a ValueResolver<string, bool> which took the PublicationCode as an input parameter, and returned a bool indicating the location of the publication (old or new system).
With the new version (5+?) of AutoMapper, this seems to no longer be possible. The new IValueResolver requires a unique implementation of each and every combination of source and destination models that I have, where src.PublicationCode needs to be resolved into a dst.IsInNewSystem.
Am I just trying to use the value resolvers in the wrong way? Is there a better way? The main reason I would like to use a resolver is that I would prefer to have services injected into the constructor, and not having to use DependencyResolver and the like in the code (I'm using Autofac).
Currently, I use it in the following way:
// Class from Linq-to-SQL, non-related properties removed.
public class FindCustomerServiceSellOffers {
public string PublicationCode { get; set; }
}
This is one of several data model classes I have, which contains a PublicationCode property). This particular class is mapped to this view model:
public class SalesPitchViewModel {
public bool IsInNewSystem { get; set; }
}
The mapping definition for these two classes is (where expression is an IProfileExpression), non-related mappings removed:
expression.CreateMap<FindCustomerServiceSellOffers, SalesPitchViewModel>()
.ForMember(d => d.IsInNewSystem, o => o.ResolveUsing<PublicationSystemResolver>().FromMember(s => s.PublicationCode));
And the resolver:
public class PublicationSystemResolver : ValueResolver<string, bool>
{
private readonly PublicationService _publicationService;
public PublicationSystemResolver(PublicationService publicationService)
{
_publicationService = publicationService;
}
protected override bool ResolveCore(string publicationCode)
{
return _publicationService.IsInNewSystem(publicationCode);
}
}
And the use of the mapper:
var result = context.FindCustomerServiceSellOffers.Where(o => someCriteria).Select(_mapper.Map<SalesPitchViewModel>).ToList();
You can create a more general value resolver by implementing IMemberValueResolver<object, object, string, bool> and using that in your mapping configuration. You can provide a source property resolution function as before:
public class PublicationSystemResolver : IMemberValueResolver<object, object, string, bool>
{
private readonly PublicationService _publicationService;
public PublicationSystemResolver(PublicationService publicationService)
{
this._publicationService = publicationService;
}
public bool Resolve(object source, object destination, string sourceMember, bool destMember, ResolutionContext context)
{
return _publicationService.IsInNewSystem(sourceMember);
}
}
cfg.CreateMap<FindCustomerServiceSellOffers, SalesPitchViewModel>()
.ForMember(dest => dest.IsInNewSystem,
src => src.ResolveUsing<PublicationSystemResolver, string>(s => s.PublicationCode));
So from my side, I want to add a few little things; try it
builder.Services.AddAutoMapper(typeof(TransactionProfile).Assembly); // working
builder.Services.AddAutoMapper(x => x.AddProfile<(TransactionProfile)>()); // not working
builder.Services.AddAutoMapper(x => x.AddMaps("Outlay.Infrastructure")); // not working
Related
I'm trying to migrate one of my modules from Postgres (with EF) to Cassandra.
Here is my best try for Cassandra mappings:
internal sealed class UserMappings : Mappings
{
public UserMappings()
{
For<User>().TableName("users")
.PartitionKey(x => x.Id)
.ClusteringKey(x => x.Id)
.Column(x => x.Id, x => x.WithDbType<Guid>().WithName("id"))
// I want to add mappings for password Hash here
}
}
The first problem is that I use VO for completive safety but want to store primitives in database.
Example VO for entity id:
public record UserId
{
public Guid Value { get; }
public UserId(Guid value)
{
if (value == Guid.Empty) throw new Exception("Invalid UserId");
Value = value;
}
public static implicit operator Guid(UserId id) => id.Value;
public static implicit operator UserId(Guid id) => new(id);
}
Secondly, my entity has private fields and I don't know how to map them to the database.
internal class User
{
private User()
{
}
public User(/*...*/)
{
//...
}
private string _passwordHash;
public UserId Id { get; }
//...
}
Also is public parameterless constructor required?
It sounds like you want to have some business logic in the classes that you are using to map to your database. I would recommend creating new classes that have only public properties and no logic whatsoever (i.e. POCOs) and then mapping these objects into your domain objects either "manually" or with a library like AutoMapper. This has the benefit of keeping your domain objects separate from the database schema.
The DataStax C# driver mapper will not be able to map private fields or properties that don't have a setter. It is able to map properties with a private setter though so you might want to leverage that instead.
Also keep in mind that you will need to provide a custom TypeConverter to the Mapper or Table objects if you use custom types in your mapping. You might get away with not implementing a TypeConveter if you have implicit operators to convert these types (like your UserId class) but I'm not 100% sure.
On the constructor issue, I think having a private empty constructor is enough.
In my project I'm trying to use automapper to unflatten my command objects to my domain objects by convention as much as possible.
It works when I explicitly map the two members in the mapping profile, but according to the automapper documentation I think this should also work by convention.
I created a dotnetfiddle to demonstrate the minimal case.
Related questions end up with people explicitly adding the mapping, but that kind of goes against what Automapper is built for and contradicts the documentation, no?
It doesn't work with flattening either, so the reversemap is a red herring I think.
The mapping
public class Mapping: Profile
{
public Mapping()
{
this.CreateMap<CreateSelectionCommand, Selection>();
// .ForMember(selection => selection.Name, opt => opt.MapFrom(x => x.SelectionName))
.reverseMap()
}
}
What I expect to work
[Fact]
public void ShouldMapName()
{
var cmd = new CreateSelectionCommand {SelectionName = "selectionName"};
var selection = _mapper.Map<Selection>(cmd);
Assert.Equal(cmd.SelectionName, selection.Name); <== selection.Name == ""
}
Classes for context
public class Selection
{
public string Name { get; set; }
}
public class CreateSelectionCommand
{
public string SelectionName { get; set; }
}
Did I misread the docs or am I missing something?
Flattening is about mapping nested "complex" objects to properties on "higher" level i.e. in your case if CreateSelectionCommand had property Selection of type which had Name property it would be mapped to SelectionName in destination type (see this fiddle).
You can try to use prefixes by adding:
cfg.RecognizePrefixes("Selection");
to your configuration (see this fiddle) but I doubt that it is suitable option for convention based handling.
Also it seems that you can add custom name convention using ISourceToDestinationNameMapper and AddMemberConfiguration:
class TypeNamePrefixedSourceToDestinationNameMapper : ISourceToDestinationNameMapper
{
public MemberInfo GetMatchingMemberInfo(IGetTypeInfoMembers getTypeInfoMembers, TypeDetails typeInfo,
Type destType,
Type destMemberType, string nameToSearch)
{
return getTypeInfoMembers.GetMemberInfos(typeInfo)
.FirstOrDefault(mi => mi.Name == destType.Name + nameToSearch);
}
}
var config = new MapperConfiguration(cfg =>
{
cfg.AddMemberConfiguration().AddName<TypeNamePrefixedSourceToDestinationNameMapper>();
// ...
}
At least it works in this simple case, see this fiddle.
guys.
Maybe someone has the same problem.
I have some cached variables in the MemoryCache (standard non-distributed in-memory implementation of Microsoft.Extensions.Caching.Memory.IMemoryCache). So, I also have mappings I use for Response/DTO creation. Some of them use variables from MemoryCache. But now I must always pass it through
opts =>
{
opts.Items.Add(variableName1, variableValue1);
opts.Items.Add(variableName2, variableValue2);
...
}
or I need to pass each time MemoryCache the same way.
Is it possible to set up a global configuration of ResolutionContext which allows me to pass all variables from MemoryCache I need in the time of the ResolutionContext creation? Unfortunately, BeforeMap isn't a solution - It has no DI mechanism for IMemoryCache resolving. And as I know It can be only one in the mapping structure - Automapper skips all BeforeMap after the first one.
Thank you.
Instead of using the ResolutionContext, you can implement a custom IMemberValueResolver, which can get the IMemoryCache dependency injected.
By doing so, there's no need to seed theResolutionContext with key/value pairs (being copied from the IMemoryCache).
The FromMemoryCacheResolver below resolves the value for the requested cache key from the injected IMemoryCache.
public class FromMemoryCacheResolver<TDestMember>
: IMemberValueResolver<object, object, object, TDestMember>
{
private readonly IMemoryCache _memoryCache;
public FromMemoryCacheResolver(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public TDestMember Resolve(
object source, object destination, object cacheKey, TDestMember destMember,
ResolutionContext context
)
{
if (_memoryCache.TryGetValue(cacheKey, out object value)
&& (value != null)
)
{
return (TDestMember)value;
}
return default(TDestMember);
}
}
Example
public class Source
{
public int Id { get; set; }
}
public class Target
{
public decimal DecimalValue { get; set; }
public string StringValue { get; set; }
}
Given the above Source and Target classes, you can define an AutoMapper mapping that sets a target property to the value bound to a fixed cache key (see DecimalValue rule) or a dynamic cache key (including a property value of the source object, see StringValue rule).
CreateMap<Source, Target>()
.ForMember(
o => o.DecimalValue,
opt => opt.MapFrom<FromMemoryCacheResolver<decimal>, object>(
_ => "constant-cache-key"
))
.ForMember(
o => o.StringValue,
opt => opt.MapFrom<FromMemoryCacheResolver<string>, object>(
src => $"dynamic-cache-key-{src.Id}"
));
You could override the way AutoMapper is registered in your dependency injection container and perform an action just before it is resolved. Assuming you use standard Microsoft's DI:
// Your code adding AutoMapper
services.AddAutoMapper(assembliesOrMarkerTypes);
// Remove just the IMapper
services.RemoveAll(typeof(IMapper));
// Add it again, but with filling the Items dictionary from cache
services.Add(new ServiceDescriptor(
typeof(IMapper),
sp =>
{
var memoryCache = sp.GetRequiredService<IMemoryCache>();
var valueFromCache = memoryCache.Get<string>("foo");
var mapper = new Mapper(sp.GetRequiredService<IConfigurationProvider>(), sp.GetService);
// Does not work!
// mapper.DefaultContext.Items.Add("foo", valueFromCache);
// Use Items from Options:
mapper.DefaultContext.Options.Items.Add("foo", valueFromCache);
return mapper;
},
ServiceLifetime.Transient)); // <== Default AutoMapper lifetime
There are two downsizes to this:
1) Access Items from resolution context's options, not directly
There is a check made when accessing Items in resolution context preventing from accessing them in a default context, which is a source for creating other contexts used in mapping. Luckily, there is no such check when accessing Items from options:
var items = resolutionContext.Options.Items;
So don't do that:
var items = resolutionContext.Items;
2) Don't use Map() with Action<IMappingOperationOptions>
You can't use any Map() method accepting Action<IMappingOperationOptions> because it will effectively overwrite content of Items dictionary created when initializing the mapper with entries from the mapping operation options, even if none were set. So, you can't do this:
var result = mapper.Map(source, destination, opts => otps.Items["bar"] = "bar");
Final note
Overall it's a bit of a hack and surely this code wouldn't win the beauty contest, so consider encapsulating it in some decent extension method for IServiceCollection.
I'm using AutoMapper v6.1.1 to map from my rich domain model entities to some flattened DTOs.
I'm initialising the configuration in a static class that returns an IMapper, which adds our mapping profiles and configures PreserveReferences() for all our maps.
I have declared a custom attribute against a subset of my source entity members (that only applies to members of type string).
I would like to add a global configuration to AutoMapper that allows me to call an extension method against any members with that attribute during the mapping.
Each of these members will end up in many different destination types, so I thought it would be a simple way of ensuring the extension method is always run for those members without explicitly configuring it for each new map.
A contrived example follows.
Source entity:
public class SomeEntity
{
public string PropertyWithoutCustomAttribute { get; set; }
[CustomAttribute]
public string PropertyWithCustomAttribute { get; set; }
}
Target entity:
public class SomeEntityDto
{
public string PropertyWithoutCustomAttribute { get; set; }
public string PropertyWithCustomAttribute { get; set; }
}
Extension method:
public static string AppendExclamationMark(this string source)
{
return source + "!";
}
If my source instance is defined with these values:
var source = new SomeEntity
{
PropertyWithoutCustomAttribute = "Hello",
PropertyWithCustomAttribute = "Goodbye"
};
I would expect the following statements to be true:
destination.PropertyWithoutCustomAttribute == "Hello"
destination.PropertyWithCustomAttribute == "Goodbye!"
I have become completely bogged down (and am struggling with the documentation somewhat) but I think the closest I have got is this:
cfg.ForAllPropertyMaps(
map => map.SourceType == typeof(string) &&
map.SourceMember
.GetCustomAttributes(
typeof(CustomAttribute),
true)
.Any(),
(map, configuration) => map.???);
Any help would be greatly appreciated, even if to tell me it's a terrible idea or it's not possible.
I want to have generic method to get data from database and pass model of how output data should look like.
I wrote simple method:
public IEnumerable<T> GetUsers<T>()
{
Mapper.Initialize(cfg =>
cfg.CreateMap<IQueryable<User>, IQueryable<T>>());
return OnConnect<IEnumerable<T>>(db =>
{
return db.Users.ProjectTo<T>().ToList();
});
}
Now I expected that I can do this:
var users = repo.GetUsers<UserViewModel>(); // it should be IEnumerable<UserViewModel>
var anotherUsers = repo.GetUsers<AnotherUserViewModel>(); // it should be IEnumerable<AnotherUserViewModel>
But I cant reinitialize automapper again. What should I do to make it working?
Initialize automapper only once per application startup.
You should know what types can be mapped from User already at the moment when you design a code in that case you can register all of them at a startup like this:
Mapper.Initialize(cfg => {
cfg.CreateMap<User, UserDto1>();
cfg.CreateMap<User, UserDto2>();
...
cfg.CreateMap<User, UserDtoN>();
});
Even if you will achieve it - it will not make a sense to try to map User to Order, but your architectural design will give that possibility
If you still want to do it(like I wrote in comments) - you can add somekind of marker attribute for Instance - MappableFrom(Type from), mark all DTO objects that can be used in scope of automapper. Then on initialization of your application - scan the assembly for all types that contains that attribute and register in Automapper.
You can use Profile to create all mappers follow this link http://docs.automapper.org/en/stable/Configuration.html
Another approach you can initialize in a static constructor all the mapping you want by using some naming convention
In the below code, I'm mapping from same object type to same object type
// Data or View Models
public class AddressViewModel : BaseViewModel
{
public string Address {get;set;}
public AddressViewModel()
{
this.Address ="Address";
}
}
public class UserViewModel : BaseViewModel
{
public string Name {get;set;}
public UserViewModel()
{
this.Name ="Name";
}
}
public class BaseViewModel
{
}
Repository -- here I'm using same view model you should create Models here
public class CrudRepo
{
public IEnumerable<T> GetData<T>() where T : class, new ()
{
var data = new List<T> { new T() };
return AutoMapper.Mapper.Map<IEnumerable<T>>(data);
}
}
Then in of the static constructor initialize the mappers
static HelperClass()
{
// In this case all classes are present in the current assembly
var items = Assembly.GetExecutingAssembly()
.GetTypes().Where(x =>
typeof(BaseViewModel)
.IsAssignableFrom(x))
.ToList();
AutoMapper.Mapper.Initialize(cfg =>
{
items.ForEach(x =>
{
// Here use some naming convention or attribute to find out the Source and Destination Type
//Or use a dictionary which gives you source and destination type
cfg.CreateMap(x, x);
});
});
}
Now you can create the instance of crud repository and get mapped items
var userRepo = new CrudRepo();
var users = userRepo.GetData<UserViewModel>();
var address = addressRepo.GetData<AddressViewModel>();
Note: As long as property names and types are same the data will be mapped else you have to create ForMember