9

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.

1
  • That works for me. Try upgrading AM. A repro would help. Make a gist that we can execute and see fail. Commented Feb 25, 2019 at 5:06

4 Answers 4

25

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()
1
  • Hmm... Would be interesting to hear full story behind this - why this is needed anyway ? I guess main idea is to simplify C# language itself, but now it's taking backing turn of making things more complex. Is there any reason for this to exists ? Commented Oct 2, 2021 at 9:30
8

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());
1
  • it seems like it for IQueryable, I would like to find out how, thanks
    – Rafi Henig
    Commented Dec 4, 2020 at 11:31
5

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

0
0

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)));

Not the answer you're looking for? Browse other questions tagged or ask your own question.