For certain properties, when the value is 0, I want to convert the value to NULL.
When I create my map I use a custom resolver to handle the conversion for me, which looks like:
AutoMapper.Mapper.CreateMap<ContactData, Contact>()
.ForMember(x => x.ContactTypeId, y => y.ResolveUsing<ContactTypeIdZeroToNullResolver>());
And my resolver looks like:
using AutoMapper;
namespace MyCompany.MyProduct.Customers.Data.Helpers
{
public class ContactTypeIdZeroToNullResolver : ValueResolver<ContactData, int?>
{
protected override int? ResolveCore(ContactData source)
{
if (source.ContactTypeId == 0)
return null;
return source.ContactTypeId;
}
}
}
This works just fine!
My question is, is it possible to make this more generic? I tried replace ContactData with just int, but errors keep being thrown.
How about something like this:
AutoMapper.Mapper.CreateMap<int, int?>()
.ConvertUsing(src => src == 0 ? (int?)null : src);
AutoMapper.Mapper.CreateMap<ContactData, Contact>();
AutoMapper.Mapper.AssertConfigurationIsValid();
(assuming of course that ContactTypeId is an int in ContactData and an int? in Contact)
You should be careful with this as it will apply to ALL your mappings for these types.
You can also inline it like this:
AutoMapper.Mapper.CreateMap<ContactData, Contact>()
.ForMember(dest => dest.ContactTypeId,
opt => opt.MapFrom(src => src.ContactTypeId == 0
? (int?)null
: src.ContactTypeId));
which will achieve the same thing, but wont be as generic.
Related
It is easy ignoring null values in the source, but then the destination will have the default value. Instead, I want the destination to keep its original value. This seems to do the job:
public static Action<IMemberConfigurationExpression<TSource, TDestination, object>> IgnoreNullInSource<TSource, TDestination>()
{
return x =>
{
x.Condition((s, d, sm) =>
{
return sm != null;
});
x.UseDestinationValue();
};
}
CreateMap<MySourceType, MyDestinationType>()
.ForAllMembers(IgnoreNullInSource<MySourceType, MyDestinationType>());
It is reusable, but I don't like repeating the generic types. Plus I feel like I am overcomplicating things. Is there a better or even build in way, with converters maybe? I did not find a way to use a converter (or memberconverter) with .ForAllMembers().
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.
Previously when I used Automapper v3.x ignoring unmapped properties could be done by simply adding a .IgnoreUnmappedProperties() extension which looked like this
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDestination> IgnoreUnmappedProperties<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
if (typeMap != null)
{
foreach (var unmappedPropertyName in typeMap.GetUnmappedPropertyNames())
{
expression.ForMember(unmappedPropertyName, opt => opt.Ignore());
}
}
return expression;
}
}
How can this extension be rewritten to work with Version 5.x. I can of course add the following to each property.
.ForMember(dest => dest.LastUpdatedBy, opt => opt.Ignore())
or not call
Mapper.AssertConfigurationIsValid();
You can do that using the CreateMap method's memberList parameter to specify the validation that you want.
CreateMap<TSource, TDestination>(MemberList.None)
The MemberList.None should do the trick. You can also switch between the source or destination validations.
Automapper - Selecting members to validate
I am trying to write this code in a more generic fashion:Is it possible that based on T i can use the right entityframework entity? So for example if I would use :
public IQueryable<T> GetCount(string filterExpression)
{
//return db.Persons.Where("it." + filterExpression);
return db. ? .Where("it." + filterExpression); // depending on type T
}
UPDATE
so now I did this:
public int GetCount<T>(string filter)
where T : class
{
NortwindEntities db = new NortwindEntities();
return db.CreateObjectSet<T>().Where(filter).Count();
}
error:
Error 2 The constraints for type parameter 'T' of method 'MyBase<T>.GetCount<T>(string)' must match the constraints for type parameter 'T' of interface method 'MyBase<T>.GetCount<T>(string)'. Consider using an explicit interface implementation instead
Are you sure that you want a queryable of T? (the name of your method is GetCount.)
You can do this to get a IQueryable<T> from your DbContext.
public IQueryable<T> GetCount<T>(Func<T, bool> predicate)
where T : class
{
MyContext db = new MyContext();
return db.Set<T>().Where(predicate).AsQueryable();
}
IQueryable<Person> result = GetCount<Person>(x => x.Id == 1);
I suggest to use the name Where as your method name.
public IQueryable<T> Where<T>(Func<T, bool> predicate)
where T : class
{
MyContext db = new MyContext();
return db.Set<T>().Where(predicate).AsQueryable();
}
IQueryable<Person> result = Where<Person>(x => x.Id == 1);
Update
Decorate the method with where T : class if you get the following exception.
The type 'T' must be a reference type in order to use it as parameter 'TEntity' in the generic type or method ?
Update 2
Seems that you really only want the count.
public int GetCount<T>(Func<T, bool> predicate)
where T : class
{
MyContext db = new MyContext();
return db.Set<T>().Where(predicate).Count();
}
int count = GetCount<Person>(x => x.Id == 1);
I am using automapper to map source and destination objects. While I map them I get the below error.
Expression must resolve to top-level member. Parameter name: lambdaExpression
I am not able resolve the issue.
My source and destination objects are:
public partial class Source
{
private Car[] cars;
public Car[] Cars
{
get { return this.cars; }
set { this.cars = value; }
}
}
public partial class Destination
{
private OutputData output;
public OutputData Output
{
get { return this.output; }
set { this.output= value; }
}
}
public class OutputData
{
private List<Cars> cars;
public Car[] Cars
{
get { return this.cars; }
set { this.cars = value; }
}
}
I have to map Source.Cars with Destination.OutputData.Cars object. Could you please help me in this?
You are using :
Mapper.CreateMap<Source, Destination>()
.ForMember( dest => dest.OutputData.Cars,
input => input.MapFrom(i => i.Cars));
This won't work because you are using 2 level in the dest lambda.
With Automapper, you can only map to 1 level. To fix the problem you need to use a single level :
Mapper.CreateMap<Source, Destination>()
.ForMember( dest => dest.OutputData,
input => input.MapFrom(i => new OutputData{Cars=i.Cars}));
This way, you can set your cars to the destination.
Define mapping between Source and OutputData.
Mapper.CreateMap<Source, OutputData>();
Update your configuration to map Destination.Output with OutputData.
Mapper.CreateMap<Source, Destination>().ForMember( dest => dest.Output, input =>
input.MapFrom(s=>Mapper.Map<Source, OutputData>(s)));
You can do it that way:
// First: create mapping for the subtypes
Mapper.CreateMap<Source, OutputData>();
// Then: create the main mapping
Mapper.CreateMap<Source, Destination>().
// chose the destination-property and map the source itself
ForMember(dest => dest.Output, x => x.MapFrom(src => src));
That's my way to do that ;-)
ForPath works for this exact scenario.
Mapper.CreateMap<Destination, Source>().ForPath(dst => dst.OutputData.Cars, e => e.MapFrom(src => src.Cars));
This worked for me:
Mapper.CreateMap<Destination, Source>()
.ForMember(x => x.Cars, x => x.MapFrom(y => y.OutputData.Cars))
.ReverseMap();
The correct answer given by allrameest on this question should help: AutoMapper - Deep level mapping
This is what you need:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.OutputData, opt => opt.MapFrom(i => i));
Mapper.CreateMap<Source, OutputData>()
.ForMember(dest => dest.Cars, opt => opt.MapFrom(i => i.Cars));
When using the mapper, use:
var destinationObj = Mapper.Map<Source, Destination>(sourceObj)
where destinationObj is an instance of Destination and sourceObj is an instance of Source.
NOTE: You should try to move away from using Mapper.CreateMap at this point, it is obsolete and will be unsupported soon.