I am trying to map List<string> property but I want in destination object to always have default value. On the source object list can be empty or it can contain other values.
If the list is empty then the Destination property will contain only the default value that is defined in the constructor.
If the list contains some data, then Source values should be appended in the list.
Here is my code:
List<string> _accessRights;
public User()
{
_accessRights = new List<string>() { "user" };
}
public List<string> AccessRights
{
get { return _accessRights; }
set { _accessRights = value; }
}
Here is the example how I would use it.
UserModel user = new UserModel() { access_rights = new List<string>() { "power_user", "admin" } };
User dbUser = Mapper.Map<User>(user);
As a result I would like to have user, power_user, admin saved in the database.
If the access_rights was null then I would expect to have only user.
Is that possible?
You can create the map like this:
Mapper.CreateMap<UserModel, User>()
.ForMember(user => user._accessRights, opt => opt.MapFrom(src => src.accessRights != null ?
src.accessRights.Union(new User()._accessRights) : new User()._accessRights));
Related
Is there any way to get AutoMapper.Collection to skip the mapping of a collection if the source collection property is null?
In my case, the client may want to signal to the API that there is no need to update a given collection. A logical way do this would be to set the collection property to null in the dto sent from the client.
Basically:
If the source collection property is null: Do not touch the destination collection at all but leave it as-is. Do not clear it
If the source collection is not null: Do the collection mapping. If the source collection is empty this means clearing the destination collection
Is there any way to achieve this in AutoMapper.Collection? What I am looking for is this:
// Sample Classes
public class Entity
{
public ICollection<EntityChild> Children { get; set; }
}
public class EntityChild
{
public int Id { get; set; }
public string Value { get; set; }
}
public class Dto
{
public ICollection<DtoChild> Children { get; set; }
}
public class DtoChild
{
public int Id { get; set; }
public string Value { get; set; }
}
// AutoMapper setup including equality for children
CreateMap<Dto, Entity>();
CreateMap<DtoChild, EntityChild>()
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ReverseMap();
// Sample 1, null source collection
var entity = new Entity
{
Children = new List<EntityChild>
{
new() { Id = 1, Value = "Value 1" },
new() { Id = 2, Value = "Value 2" }
}
};
var dtoSkipChildren = new Dto
{
Children = null
};
// Since the source Children property is null, do not update the destination collection
mapper.Map(dtoSkipChildren, entity);
// Sample 2, empty source collection
entity = new Entity
{
Children = new List<EntityChild>
{
new() { Id = 1, Value = "Value 1" },
new() { Id = 2, Value = "Value 2" }
}
};
var dtoClearChildren = new Dto
{
Children = new List<DtoChild>()
};
// Now the source children is not null (but empty) so the destination collection should
// be updated (in this case cleared since the source collection is empty)
mapper.Map(dtoClearChildren, entity);
AutoMapper.Collection treats the null source Children property the same as the source Children property containing an empty collection. In both cases the destination Children collection is cleared.
I have tried to tell AutoMapper to skip the source.Children property if null:
CreateMap<Dto, Entity>()
.ForMember(dst => dst.Children, opt => opt.Condition(src => null != src.Children));
This does not change things. Also in this case, the source collection property is null when AutoMapper.Collection sets to work and the destination collection is cleared.
This is not a real solution either:
CreateMap<Dto, Entity>()
.ForMember(
src => src.Children,
opt => opt.MapFrom((src, dst, _, ctx) => src.Children ?? ctx.Mapper.Map<ICollection<DtoChild>>(dst.Children)));
This means reverse mapping the destination collection to the (null) source collection, so it can be mapped back. An ugly hack:
It's crossing the river twice to end up where you started
Wasted effort which is silly on large collections
Risky as the reverse map for other reasons may not be 100% thus introducing pretty hidden bugs
Does anyone have an advice on how to achieve this - or why my use case is not a good idea?
I had the same problem and I figured it out this way
CreateMap<Dto, Entity>()
.ForMember(
dest=> dest.Children,
opt => {
opt.PreCondition(src =>src.Children!= null);
opt.MapFrom(src =>src.Children);
});
Then in the place, you want to use mapping write code like this:
entity = mapper.Map<Dto, Entity>(dto , entity);
opt.PreCondition(src =>src.Children!= null) basically says to AutoMapper to proceed to map if the source field is not empty else do not map.
to read more look at this https://docs.automapper.org/en/stable/Conditional-mapping.html#preconditions
For some reason AutoMapper gives me a list of the type I need, where all properties are 0 or null. When I debug on data I see my list with all object and properties containing data. But .Map() gives me a list with no data in the properties (but the correct amount of objects). I'm new to AutoMapper, but this does seem very weird. Any suggestions?
public static IMapper Initialize()
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<PlantSelectResult, IPlant>()
.ForMember(
dest => dest.description,
opt => opt.MapFrom(src => src.description));
});
return new Mapper(config);
}
And my DataProvider:
public IEnumerable<IPlant> GetPlants()
{
using (var dbCtx = new DataClasses1DataContext(_connectionString.String))
{
var data = dbCtx.PlantSelect().ToList();
return automapper.Map<List<PlantSelectResult>, IPlant[]>(data);
}
}
I didn't realize I had removed set; on the properties. Fixing the interfaces so they were settable fixed the problem.
I have an example like this :
class Fields
{
string ContactOneName{get;set;}
string ContactOnePhone{get;set;}
string ContactOneSpouseName{get;set;}
string ContactOneSpousePhone{get;set;}
}
And I would like to map to a model like this:
class Contacts
{
Contact ContactOne {get;set;}
Contact ContactOneSpouse {get;set;}
}
class Contact
{
string Name {get;set;}
string Phone {get;set;}
}
There are lots of fields and I don't want to write a mapping for each field.
Is this possible?
If so how?
NB: This question is almost a duplicate of AutoMapper unflattening complex objects of same type but I want a solution NOT manually mapping everything, because in that case it is not worth using automapper.
You can take this and add:
public static IMappingExpression<TSource, TDestination> ForAllMembers<TSource, TDestination, TMember>(
this IMappingExpression<TSource, TDestination> mapping,
Action<IMemberConfigurationExpression<TSource, TDestination, TMember>> opt)
{
var memberType = typeof(TMember);
var destinationType = typeof(TDestination);
foreach(var prop in destinationType.GetProperties().Where(prop => prop.PropertyType.Equals(memberType)))
{
var parameter = Expression.Parameter(destinationType);
var destinationMember = Expression.Lambda<Func<TDestination, TMember>>(Expression.Property(parameter, prop), parameter);
mapping.ForMember(destinationMember, opt);
}
return mapping;
}
Then you can configure the mapping as follows:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Fields, Contacts>().ForAllMembers<Fields, Contacts, Contact>(x => { x.Unflatten(); });
});
I want to create the table with custom name but I cannot find the sample code. I notice the only way to create table is by generic type like db.CreateTable(). May I know if there is a way to create the table name dynamically instead of using Alias? The reason is because sometime we want to store the same object type into different tables like 2015_january_activity, 2015_february_activity.
Apart from this, the db.Insert also very limited to object type. Is there anyway to insert by passing in the table name?
I think these features are very important as it exists in NoSQL solution for long and it's very flexible. Thanks.
OrmLite is primarily a code-first ORM which uses typed POCO's to create and query the schema of matching RDMBS tables. It also supports executing Custom SQL using the Custom SQL API's.
One option to use a different table name is to change the Alias at runtime as seen in this previous answer where you can create custom extension methods to modify the name of the table, e.g:
public static class GenericTableExtensions
{
static object ExecWithAlias<T>(string table, Func<object> fn)
{
var modelDef = typeof(T).GetModelMetadata();
lock (modelDef) {
var hold = modelDef.Alias;
try {
modelDef.Alias = table;
return fn();
}
finally {
modelDef.Alias = hold;
}
}
}
public static void DropAndCreateTable<T>(this IDbConnection db, string table) {
ExecWithAlias<T>(table, () => { db.DropAndCreateTable<T>(); return null; });
}
public static long Insert<T>(this IDbConnection db, string table, T obj, bool selectIdentity = false) {
return (long)ExecWithAlias<T>(table, () => db.Insert(obj, selectIdentity));
}
public static List<T> Select<T>(this IDbConnection db, string table, Func<SqlExpression<T>, SqlExpression<T>> expression) {
return (List<T>)ExecWithAlias<T>(table, () => db.Select(expression));
}
public static int Update<T>(this IDbConnection db, string table, T item, Expression<Func<T, bool>> where) {
return (int)ExecWithAlias<T>(table, () => db.Update(item, where));
}
}
These extension methods provide additional API's that let you change the name of the table used, e.g:
var tableName = "TableA"'
db.DropAndCreateTable<GenericEntity>(tableName);
db.Insert(tableName, new GenericEntity { Id = 1, ColumnA = "A" });
var rows = db.Select<GenericEntity>(tableName, q =>
q.Where(x => x.ColumnA == "A"));
rows.PrintDump();
db.Update(tableName, new GenericEntity { ColumnA = "B" },
where: q => q.ColumnA == "A");
rows = db.Select<GenericEntity>(tableName, q =>
q.Where(x => x.ColumnA == "B"));
rows.PrintDump();
This example is also available in the GenericTableExpressions.cs integration test.
I am having some issues in the mapping mentioned in the title. Here are the details:
class MyDomain
{
public Iesi.Collections.Generic.ISet<SomeType> MySomeTypes{ get; set; }
....
}
class MyDTO
{
public IList<SomeTypeDTO> MySomeTypes{ get; set; }
...
}
The mapping:
Mapper.CreateMap<MyDomain, MyDTO>().ForMember(dto=>dto.MySomeTypes, opt.ResolveUsing<DomaintoDTOMySomeTypesResolver>());
Mapper.CreateMap<MyDTO, MyDomain>().ForMember(domain=>domain.MySomeTypes, opt.ResolveUsing<DTOtoDomainMySomeTypesResolver>());
The Resolvers:
class DomaintoDTOMySomeTypesResolver: ValueResolver<MyDomain, IList<SomeTypeDTO>>
{
protected override IList<SomeTypeDTO> ResolveCore(MyDomain source)
{
IList<SomeTypeDTO> abc = new List<DemandClassConfigurationDTO>();
//Do custom mapping
return abc;
}
}
class DTOtoDomainMySomeTypesResolver: ValueResolver<MyDTO, Iesi.Collections.Generic.ISet<SomeType>>
{
protected override Iesi.Collections.Generic.ISet<SomeType> ResolveCore(SystemParameterDTO source)
{
Iesi.Collections.Generic.ISet<SomeType> abc = new HashedSet<SomeType>();
//Do custom mapping
return abc;
}
}
Mapping from Domain to DTO works ok and as expected I get a MyDTO object with IList of "SomeTypeDTO" objects.
However mapping of the DTO to Domain throws the following error:
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown.
----> AutoMapper.AutoMapperMappingException : Trying to map Iesi.Collections.Generic.HashedSet`1[SomeType, MyAssembly...] to Iesi.Collections.Generic.ISet`1[SomeType, MyAssembly...]
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown.
----> System.InvalidCastException : Unable to cast object of type 'System.Collections.Generic.List`1[SomeType]' to type 'Iesi.Collections.Generic.ISet`1[SomeType]
What might I be doing wrong and what do the error messages imply? It almost seems that automapper is having some issues in mapping the ISet ( together with its concrete implementation HashedSet). My understanding is that in the above described scenario automapper should just use the ISet reference returned by "DTOtoDomainMySomeTypesResolver". I also don't see why I am getting the "cast from List to ISet error".
This is because AutoMapper currently doesn't support ISet<> collection properties. It works when the destination property of ISet<> is already instantiated (is not null), because the ISet<> actually inherits from ICollection<>, thus Automapper can understand that and will do the collection mapping properly.
It doesn't work when the destination property is null and is interface type. You get this error, because automapper actually found out it can be assigned from ICollection<> so it instantiates the property using generic List<>, which is default collection when automapper must create new collection property, but then when it tries to actually assign it, it will fail, because obviously List<> cannot be cast to ISet<>
There are three solution to this:
Create a feature request to support ISet<> collections and hope they will add it
Make sure the property is not null. Eg. instantiate it in constructor to empty HashSet<>. This might cause some troubles for ORM layers, but is doable
The best solution that I went with is to create custom value resolver, which you already have and instantiate the property yourself if it is null. You need to implement the IValueResolver, because the provided base ValueResolver will not let you instantiate the property. Here is the code snippet that I used:
public class EntityCollectionMerge : IValueResolver
where TDest : IEntityWithId
where TSource : IDtoWithId
{
public ResolutionResult Resolve(ResolutionResult source)
{
//if source collection is not enumerable return
var sourceCollection = source.Value as IEnumerable;
if (sourceCollection == null) return source.New(null, typeof(IEnumerable));
//if the destination collection is ISet
if (typeof(ISet).IsAssignableFrom(source.Context.DestinationType))
{
//get the destination ISet
var destSet = source.Context.PropertyMap.GetDestinationValue(source.Context.DestinationValue) as ISet;
//if destination set is null, instantiate it
if (destSet == null)
{
destSet = new HashSet();
source.Context.PropertyMap.DestinationProperty.SetValue(source.Context.DestinationValue, destSet);
}
Merge(sourceCollection, destSet);
return source.New(destSet);
}
if (typeof(ICollection).IsAssignableFrom(source.Context.DestinationType))
{
//get the destination collection
var destCollection = source.Context.PropertyMap.GetDestinationValue(source.Context.DestinationValue) as ICollection;
//if destination collection is null, instantiate it
if (destCollection == null)
{
destCollection = new List();
source.Context.PropertyMap.DestinationProperty.SetValue(source.Context.DestinationValue, destCollection);
}
Merge(sourceCollection, destCollection);
return source.New(destCollection);
}
throw new ArgumentException("Only ISet and ICollection are supported at the moment.");
}
public static void Merge(IEnumerable source, ICollection destination)
{
if (source == null) return;
var destinationIds = destination.Select(x => x.Id).ToHashSet();
var sourceDtos = source.ToDictionary(x => x.Id);
//add new or update
foreach (var sourceDto in sourceDtos)
{
//if the source doesnt exist in destionation add it
if (sourceDto.Key (sourceDto.Value));
continue;
}
//update exisiting one
Mapper.Map(sourceDto.Value, destination.First(x => x.Id == sourceDto.Key));
}
//delete entity in destination which were removed from source dto
foreach (var entityToDelete in destination.Where(entity => !sourceDtos.ContainsKey(entity.Id)).ToList())
{
destination.Remove(entityToDelete);
}
}
}
Then on your mapping use opt => opt.ResolveUsing(new EntitCollectionMerge<Entity,Dto>()).FromMember(x => x.ISetMember) or if you have lots of collection like this you can add them automatically to all of them via typeMaps.