There are two classes:
public class User
{
public int id;
public string username;
// lots more...
}
public class UserEx : User
{
public string extraData;
}
And this is the CreateMap that is currently being used:
profile.CreateMap< Tuple<User, string>, UserEx>()
.ForMember( d => d.id, opt => opt.MapFrom( s => s.Item1.id ) )
.ForMember( d => d.username, opt => opt.MapFrom( s => s.Item1.username ) )
// lots and lots more mappings of s.Item1 to d
.ForMember( d => d.extraData, opt => opt.MapFrom( s => s.Item2.extraData ) );
The User class has a ton of fields, is there some automatic way to get it to map s.Item1 to the destination?
I did look at the doc's on flattening > IncludeMembers and came up with the following which does NOT work:
.Ignore( d => d.extraData );
profile.CreateMap< Tuple<User, string>, UserEx>()
.IncludeMembers( t => t.Item1 )
.ForMember( d => d.extraData, opt => opt.MapFrom( s => s.Item2.extraData ) );
I took #PanagiotisKanavos's and kept it simple:
profile.CreateMap< User, UserEx>()
.Ignore( d => d.extraData );
UserEx MakeUserEx(User u, string extraData)
{
var userEx = mapper.Map<UserEx>(u);
userEx.extraData = extraData;
return userEx;
}
var listOfUserEx = from u in UserList
join ex in extraDataList on u.id equals ex.id
select MakeUserEx(u, ex.extraData)
I tried to figure out how to make the MakeUser a lambda function, but this works well and after a bit I figured it was time to move on.
Related
I am using automapper to map from model to dto. In my model I want to use a string where in my dto I use an Enum. While it is a nested child, I am using ForPath instead ForMember. To convert to string is easy, however to convert the string back to type I wrote a ValueConverter. Using a ValueConverter in combination with ForMember is working excellent, however now I need to use it with ForPath which is not possible. Are there any other solutions to solve this problem, while I cannot find it in the automapper documentation or on stack.
This is my MappingProfile
this part is working with member:
CreateMap<Dto, Model>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type.ToString()))
.ReverseMap()
.ForMember(dest => dest.Type, opt => opt.ConvertUsing(new StringToEnumConverter<Type>(), src => src.Type));
this part I need ForPath and ConvertUsing, this code is not allowed
CreateMap<Dto, Model>()
.ForPath(dest => dest.Type, opt => opt.MapFrom(src => src.Parent.Type.ToString()))
.ReverseMap()
.ForPath(dest => dest.Parent.Type, opt => opt.ConvertUsing(new StringToEnumConverter<Type>(), src => src.Type));
and this is my ValueConverter:
public class StringToEnumConverter<T> : IValueConverter<string, T> where T : struct
{
public T Convert(string source, ResolutionContext context)
{
if (!string.IsNullOrEmpty(source))
{
if (Enum.TryParse(source, out T result))
{
return result;
}
}
return default;
}
}
Thanks to Lucian Bargaoanu I came up with this solution. I rewrote the valueConverter to be just a static class and use MapFrom to convert.
public static class StringEnumExtension
{
public static TaskType ToTaskType(this string source)
{
return ToEnum<TaskType>(source);
}
public static TaskQuestionType ToTaskQuestionType(this string source)
{
return ToEnum<TaskQuestionType>(source);
}
private static T ToEnum<T>(string source) where T : struct
{
if (!string.IsNullOrEmpty(source))
{
if (Enum.TryParse(source, out T result))
{
return result;
}
}
return default;
}
}
And I updated the mapping like this:
CreateMap<TaskDto, TaskModel>()
.ForPath(dest => dest.TaskType, opt => opt.MapFrom(src => src.TaskDefinition.TaskType.ToString()))
.ReverseMap()
.ForPath(dest => dest.TaskDefinition.TaskType, opt => opt.MapFrom(src => src.TaskType.ToTaskType()));
I prefer additional maps within the same profile over using ForPath. This way I can still use my custom value resolvers:
public class DstObject
{
public int AnotherProperty { get; set; }
public DstChildObject DstChildObject { get; set; }
}
public class DstChildObject
{
public string SomeProperty { get; set; }
}
public class MyMappingProfile : Profile
{
public MyMappingProfile()
{
this.CreateMap<SourceType, DstObject>()
.ForMember(dst => dst.AnotherProperty, opt => opt.MapFrom(src => src.AnotherProperty))
.ForMember(dst => dst.DstChildObject, opt => opt.MapFrom(src => src))
;
this.CreateMap<SourceType, DstChildObject>()
.ForMember(dst => dst.SomeProperty, opt => opt.MapFrom(src => src.SomeProperty))
;
}
}
I want the null value during mapping DTO to DBO model to be ignored. This is the code:
DTO / DBO models have both property named items:
public virtual ICollection<price_list_item> items { get; set; }
DBO constructor:
public price_list()
{
this.items = new List<price_list_item>();
}
DTO constructor has no propert initialization
public price_list()
{
}
AutoMapper Profile:
this.CreateMap<DTO.price_list, DBO.price_list>()
.ForMember(m => m.id, src => src.Ignore())
.ForMember(m => m.currency_id, src => src.MapFrom(f => f.currency))
.ForMember(dest => dest.items, opt => opt.Condition(src => (src.items != null)))
API Controller:
[HttpPut]
[Route("{id:long}")]
public async Task<DTO.price_list> UpdateOneAsync(long id, [FromBody]DTO.price_list payload)
{
if (payload == null)
{
throw new ArgumentNullException("payload");
}
Console.WriteLine(payload.items == null);
var _entity = await this.IDataRepository.price_lists
.Where(w => w.id == id)
.Include(i => i.items)
.FirstOrDefaultAsync();
if (_entity == null)
{
NotFound();
return null;
}
Console.WriteLine(_entity.items.Count);
// map fields to existing model
this.IMapper.Map<DTO.price_list, DBO.price_list>(payload, _entity);
Console.WriteLine(_entity.items.Count);
When I send to API a JSON without any sign of 'items' property, Console output is:
True
1200 // price list in dbo has 1200 items
0 // here I need to have still 1200 items
What am I doing wrong? Why the condition is not respected and items property is not 'skiped' ?
Thanks
Lucian thanks, PreCondition solved the problem. This is working code:
this.CreateMap<DTO.price_list, DBO.price_list>()
.ForMember(m => m.id, src => src.Ignore())
.ForMember(m => m.currency_id, src => src.MapFrom(f => f.currency))
.ForMember(dest => dest.items, opt => opt.PreCondition(src => (src.items != null)))
I'm trying to map a viewmodel to an entity from EF. I have gotten so far that I can map all properties from the viewmodel to the entity but I am trying to ignore all properties that is null (the data in the viewmodel comes from a form and I don't want to put null as the pk for example). I want to be able to do something like this:
IUserDetails objUserDetails = GetDataFromForm();
var user = db.Users.FirstOrDefault();
user.UpdateUser(objUserDetails);
This is what I have come up with so far:
public static class UserExtensions
{
public static void UpdateUser(this IUser user, IUserDetails userDetails)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<IUserDetails, IUser>()
.UseDestinationValue()
.IgnoreNullValues()
.ForMember(dest => dest.AddressLine1, opt => opt.MapFrom(src => src.Address1))
.ForMember(dest => dest.AddressLine2, opt => opt.MapFrom(src => src.Address2))
.ForMember(dest => dest.LName, opt => opt.MapFrom(src => src.LastName))
.ForMember(dest => dest.UserGUID, opt => opt.MapFrom(src => src.GUID));
});
var mapper = config.CreateMapper();
mapper.Map<IUserDetails, IUser>(userDetails, user);
}
private static IMappingExpression<TSource, TDest> UseDestinationValue<TSource, TDest>(this IMappingExpression<TSource, TDest> expression)
{
expression.ForAllMembers(opt => opt.UseDestinationValue());
return expression;
}
private static IMappingExpression<TSource, TDest> IgnoreNullValues<TSource, TDest>(this IMappingExpression<TSource, TDest> expression)
{
expression.ForAllMembers(opt => opt.Condition((src, dest, srcVal, destVal, c) => { Debugger.Break(); return srcVal != null; }));
return expression;
}
}
As you can see I have put a breakpoint in IgnoreNullValues() and when I debug the code I can see that srcVal is never anything else than null, however all the other values looks good. What am I missing?
Update:
Apparently this is due to a bug in 5.1.1, downgrading to 5.0.2 made it all work.
I have an entity that contains a DbGeography type that has a multi-polygon. I have mapped the DbGeography to a string[][]:
internal class DbGeographyAutoMapper : Profile
{
public DbGeographyAutoMapper()
{
CreateMap<DbGeography, string[][]>()
.ConvertUsing(geo =>
{
var maxElement = geo.ElementCount + 1;
var rings = new string[geo.ElementCount.Value][];
for (var elementIndex = 1; elementIndex < maxElement; elementIndex++)
{
var currentElement = geo.ElementAt(elementIndex);
var latLngs = new string[currentElement.PointCount.Value];
var max = currentElement.PointCount + 1;
for (var i = 1; i < max; i++)
{
var point = currentElement.PointAt(i).EndPoint;
latLngs[i - 1] = $"{Math.Round(point.Latitude.Value, 4)},{Math.Round(point.Longitude.Value, 4)}";
}
rings[elementIndex - 1] = latLngs;
}
return rings;
});
}
}
And the entity to a model that contains a string[][]:
internal class WireCenterAutoMapper : Profile
{
public WireCenterAutoMapper()
{
CreateMap<WireCenter, WirecenterModel>()
.ForMember(m => m.Boundary, m => m.MapFrom(wc => wc.Boundary)) // This is the DbGeography to string[][]
.ForMember(m => m.CLLI, m => m.MapFrom(wc => wc.CLLI))
.ForMember(m => m.Id, m => m.MapFrom(wc => wc.Id))
.ForMember(m => m.Name, m => m.MapFrom(wc => wc.OCN))
.ForMember(m => m.Owner, m => m.MapFrom(wc => wc.Incumbent))
.ForMember(m => m.State, m => m.MapFrom(wc => wc.State));
}
}
When I try and project it to an IQueryable ...
[EnableQuery(PageSize = 500)]
public async Task<IQueryable<WirecenterModel>> Get([FromUri] BoundingBox intersects = null)
{
return (await _wireCentersRepository.Find(intersects)).ProjectTo<WirecenterModel>();
}
... it blows up because the DbGeography uses a convert using. I would like to have that property mapped when converting it from the entity to the model, but ignored when projecting it to an EF Query. I did mark it as NotMapped...
/// <summary>
/// The Wire Center's boundary
/// </summary>
[NotMapped]
public string[][] Boundary { get; set; }
... but this did not help. How do I do this?
I had a similar issue where I didn't want to map specifically during a ProjectTo().
What worked for myself was the ExplicitExpansion setting:
internal class WireCenterAutoMapper : Profile
{
public WireCenterAutoMapper()
{
CreateMap<WireCenter, WirecenterModel>()
.ForMember(m => m.Boundary, opt => opt.ExplicitExpansion())
//...Followed by the rest of the code
}
}
With this, LINQ projections won't map this value, leaving it null, unless you explicitly request it to be mapped in a projection.
I'm looking for a way to get total price count from the Costs list in my object. I can't get Projections.Sum to work in my QueryOver so I tried another way but I'm having problems with it. I want to use a unmapped property in my QueryOver. I found this example but it's giving an error.
Object:
public class Participant
{
public int Id { get; set; }
public double TotalPersonalCosts { get { return Costs.Where(x => x.Code.Equals("Persoonlijk") && x.CostApprovalStatus == CostApprovalStatus.AdministratorApproved).Sum(x => x.Price.Amount); } }
public IList<Cost> Costs { get; set; }
}
The property TotalPersonalCosts is not mapped and contains the total price count.
Extension Class:
public static class ParticipantExtensions
{
private static string BuildPropertyName(string alias, string property)
{
if (!string.IsNullOrEmpty(alias))
{
return string.Format("{0}.{1}", alias, property);
}
return property;
}
public static IProjection ProcessTotalPersonalCosts(System.Linq.Expressions.Expression expr)
{
Expression<Func<Participant, double>> w = r => r.TotalPersonalCosts;
string aliasName = ExpressionProcessor.FindMemberExpression(expr);
string totalPersonalCostName = ExpressionProcessor.FindMemberExpression(w.Body);
PropertyProjection totalPersonalCostProjection =
Projections.Property(BuildPropertyName(aliasName, totalPersonalCostName));
return totalPersonalCostProjection;
}
}
My QueryOver:
public override PagedList<AccountantViewInfo> Execute()
{
ExpressionProcessor.RegisterCustomProjection(
() => default(Participant).TotalPersonalCosts,
expr => ParticipantExtensions.ProcessTotalPersonalCosts(expr.Expression));
AccountantViewInfo infoLine = null;
Trip tr = null;
Participant pa = null;
Cost c = null;
Price p = null;
var infoLines = Session.QueryOver(() => tr)
.JoinAlias(() => tr.Participants, () => pa);
if (_status == 0)
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPrinted || pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPaid);
else if (_status == 1)
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPrinted);
else
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPaid);
infoLines.WhereRestrictionOn(() => pa.Employee.Id).IsIn(_employeeIds)
.Select(
Projections.Property("pa.Id").WithAlias(() => infoLine.Id),
Projections.Property("pa.Employee").WithAlias(() => infoLine.Employee),
Projections.Property("pa.ProjectCode").WithAlias(() => infoLine.ProjectCode),
Projections.Property("tr.Id").WithAlias(() => infoLine.TripId),
Projections.Property("tr.Destination").WithAlias(() => infoLine.Destination),
Projections.Property("tr.Period").WithAlias(() => infoLine.Period),
Projections.Property("pa.TotalPersonalCosts").WithAlias(() => infoLine.Period)
);
infoLines.TransformUsing(Transformers.AliasToBean<AccountantViewInfo>());
var count = infoLines.List<AccountantViewInfo>().Count();
var items = infoLines.List<AccountantViewInfo>().ToList().Skip((_myPage - 1) * _itemsPerPage).Take(_itemsPerPage).Distinct();
return new PagedList<AccountantViewInfo>
{
Items = items.ToList(),
Page = _myPage,
ResultsPerPage = _itemsPerPage,
TotalResults = count,
};
}
Here the .Expression property is not found from expr.
I don't know what I'm doing wrong. Any help or alternatives would be much appreciated!
Solution with Projection.Sum() thx to xanatos
.Select(
Projections.Group(() => pa.Id).WithAlias(() => infoLine.Id),
Projections.Group(() => pa.Employee).WithAlias(() => infoLine.Employee),
Projections.Group(() => pa.ProjectCode).WithAlias(() => infoLine.ProjectCode),
Projections.Group(() => tr.Id).WithAlias(() => infoLine.TripId),
Projections.Group(() => tr.Destination).WithAlias(() => infoLine.Destination),
Projections.Group(() => tr.Period).WithAlias(() => infoLine.Period),
Projections.Sum(() => c.Price.Amount).WithAlias(() => infoLine.TotalPersonalCost)
);
You can't use unmapped columns as projection columns of a NHibernate query.
And the way you are trying to do it is conceptually wrong: the ParticipantExtensions methods will be called BEFORE executing the query to the server, and their purpose is to modify the SQL query that will be executed. An IProjection (the "thing" that is returned by ProcessTotalPersonaCosts) is a something that will be put between the SELECT and the FROM in the query. The TotalCosts can't be returned by the SQL server because the SQL doesn't know about TotalCosts