I'm using AutoMapper 5.2. I currently have a mapping statement that looks as follows:
CreateMap<JeffreysOnline.Data.Customer, JeffreysOnline.Entities.Customer>()
.ForMember(s => s.CustomerWant, t => t.Ignore());
Both the Customer table and Customer entity have a field named BadChecks. In the database it's an int. I recently changed the type to a bool in my entity. AutoMapper is now giving me the following error:
Unable to create a map expression from Customer.BadChecks (System.Int16) to Customer.BadChecks (System.Boolean) Mapping types: Customer -> Customer JeffreysOnline.Data.Customer -> JeffreysOnline.Entities.Customer Type Map configuration: Customer -> Customer JeffreysOnline.Data.Customer -> JeffreysOnline.Entities.Customer Property: BadChecks
It seems AutoMapper doesn't know how to map from an int to a boolean. Is it possible for me to help AutoMapper with this?
It may be helpful to know that in my DAL, I'm using ProjectTo() to pass an IQueryable to another method that is attempting to access the data, and therefore the mapping is occurring (an error being generated). My DAL code looks like this:
return entityList.OrderBy(row => row.LastName).ProjectTo<Entities.Customer>();
Automapper 6.0.2 - works without any ForMember... null, 0 = false, values >= 1 are mapped to true.
In Automapper 6.0.2 - other way:
class nnnProfile : Profile
{
CreateMap<src, dst>()
.ForMember(d => d.Decision, opt => opt.ResolveUsing<CustomBoolResolver>());
}
Resolver:
public class CustomBoolResolver : IValueResolver<src, dst, bool>
{
public bool Resolve(src source, dst destination, bool destMember,
ResolutionContext context)
{
return source.Decision == 1;
}
}
but this is per Destination, so not much flexible.
According to this page:
http://taswar.zeytinsoft.com/automapper-mapping-objects-part-5-of-7-customresolver/
In past you could write a custom resolver with just Source and target type.
I don't think I would know how to map from int to a boolean.
If you do figure out how that should happen, you'll need to create a mapping from int to boolean.:
CreateMap<int, bool>().ProjectUsing(src => src != 0);
Completely guessing there. But since you're using ProjectTo, you'll need to use ProjectUsing so that the expression makes it allllll the way down to your DAL.
Remember, when using ProjectUsing, AutoMapper isn't actually executing the mapping. It's creating a LINQ "Select" expression that it passes down to your query provider (EF maybe?). So you'll need to make sure that whatever you use in your projection expression, EF can support translating that eventually into SQL.
Related
After migration from an old version of AutoMapper (before 5) to version 9 there is one spot which causes headache. Old implementation:
.ForMember(a => a.Definition, o =>
{
o.Condition(s => s.TypeId == DocumentationType.Medication);
o.ResolveUsing((d, ctx) => ctx.Engine.Map<MedicationDefinitionContent>(d.Content.MedicationContentData));
})
which uses this extension method:
public static class MappingExtensions
{
public static void ResolveUsing<TType>(this IMemberConfigurationExpression<TType> expression, Func<TType, ResolutionContext, object> map)
{
expression.ResolveUsing(result => map((TType)result.Value, result.Context));
}
}
I fixed the first error that that IMemberConfigurationExpression needs 3 arguments, but then I learned that ResolutionContext does not contain a definition for engine anymore. I looked in the upgrade guide of version 5 and found that the ResolutionContext has been changed, but I do not understand how to fix this. The code seems to be pretty tricky. Can someone help, please?
#Lucian Bargaoanu
Ok, but the member "Definition" is the member wie map with MapFrom(s => s.Content.MedicationContentData). So different to the exception there is already a mapping. The member "Definition" is of type SerialisationHelper a helper class for Json stuff. It also has a mapping.
CreateMap<MedicationDefinitionContent, SerialisationHelper>()
.IgnoreAllUnmapped()
.AfterMap((s, t) => t.Write = s);
And MedicationDefinitionContent has a separate mapping.
CreateMap<MedicationContentData, MedicationDefinitionContent>()
MedicationDefinitionContent is annotated with [JsonObject(MemberSerialization.OptIn)]
so, a direct mapping from MedicationDefinitionContent to "Definition" does not work.
How you see I try to understand it, but maybe it needs more time.
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 am calling a generic method with two different classes as below:
FillDataPointsInOrder<Metrics>(dataPoints.Where(O => O.SortOrder != null).OrderBy(O => O.SortOrder));
FillDataPointsInOrder<Metric>(angieStatsCmp.GetDataColumns());
private void FillDataPointsInOrder<T>(IEnumerable<T> dataPoints)
{
foreach (T dpoint in dataPoints)
{
if (!dpoint.IsPhone)
FillDrp(this.EmailDrp, dpoint.Name, dpoint.MetricId.ToString(), dpoint.VName);
if (dpoint.IsPhone && this.IsPhoneShop)
FillDrp(this.PhoneDrp, dpoint.Name, dpoint.MetricId.ToString(), dpoint.VName);
}
}
in "FillDataPointsInOrder" method I am getting compile errors :
'T' does not contain a definition for 'IsPhone' and no extension method 'IsPhone' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?)
Same errors for Name , MetricId and VName properties.
Not sure why T is not able to access properties of Metrics and Metric.
If I remove the code from generic method and write it directly in foreach over dataPoints it is working fine.
Can somebody advise what is wrong here?
FillDataPointsInOrder only knows it will be called with a T. T could actually be string, int or anything.
If you want to call properties on T, you will have to use a where constraint.
But in this case it looks like your method does not even need to be generic.
If both Metric and Metrics share a base class or an interface that has the properties you need:
interface IMetric {
bool IsPhone {get; }
}
you could just have:
private void FillDataPointsInOrder(IEnumerable<IMetric> dataPoints)
Note that IEnumerable is covariant, so if Metric is a IMetric, IENumerable<Metric> is a IEnumerable<IMetric>
You need to at least tell the compiler something about T if you want to do that. Do you have an interface that has members like IsPhone, Name, MetricId, etc. that your classes implement?
If so you can add a 'where' constraint to your class definition:
public class Something<T> where T : ISomethingElse
...where ISomethingElse is the interface that implements IsPhone.
I want to customise the way AutoMapper converts my types without losing the features already implemented by AutoMapper.
I could create a custom ITypeConverter instance but I can't see how to invoke the default behaviour.
Mapper.CreateMap<MyDomainObject, MyDto>
.ConvertUsing<MyTypeConverter>();
...
public class MyTypeConverter : TypeConverter<MyDomainObject, MyDto>
{
public MyDto ConvertCore(MyDomainObject source)
{
var result = // Do the default mapping.
// do my custom logic
return result
}
}
If I try to call var result = Mapper.Map<MyDto>(source) it gets into an infinite loop. I effectively want AutoMapper to do everything it normally would assuming there was no TypeConverter defined.
Any help greatly appreciated.
If you only want to customise some values on the destination object, then you're better off with a Custom Value Resolver - TypeConverters are designed to handle the whole conversion.
The doc page listed above will have enough to get you started: when you have implemented the CustomResolver you apply it like this, and AutoMapper will do the default mapping for the other properties:
Mapper.CreateMap<MyDomainObject, MyDto>()
.ForMember(dest => dest.TargetProperty,
opt => opt.ResolveUsing<CustomResolver>());