I have two classes:
public class ClassA
{
public ClassA()
{
LanguageInfo = new List<ClassALanguage>();
}
public long ID { get; set; }
public string Value { get; set; }
public List<ClassALanguage> LanguageInfo { get; set; }
}
public class ClassALanguage
{
public long ID { get; set; }
public string Name { get; set; }
public string Language { get; set; }
}
And I have a destination class:
public class WSClassA
{
public long ID { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
So I thought I could configure the mapping like this:
Mapper.CreateMap<ClassA, WSClassA>()
.ForMember(
ws => ws.Name,
opt => opt.MapFrom(clsA => clsA.LanguageInfo.Find(lg => lg.Language == languageSelected).Name));
The problem is after the first time the mapping is executed, it is not able to change. Even if the CreateMap() is executed with another value for languageSelected, the binding works as the first time.
Is there any solution to accomplish that?
In your case you need some context when you perform your mapping - the selected language.
Instead of using MapFrom use the ResolveUsing method with a custom IValueResolver.
See - https://automapper.codeplex.com/wikipage?title=Custom%20Value%20Resolvers
When you make your .Map call use the overload which allows you to modify the IMappingOperationOptions. Set the language you want to compare against in the interfaces Items collection.
Finally in your custom IValueResolver's Resolve method you can access those items via ResolutionResult.Context.Options.Items property.
Related
I'm trying to get automapper working for .NET core, and I seem to be missing something important.
I have my automapper set up as follows:
In Startup.cs in my ConfigureServices method, I have the following line:
services.AddAutoMapper(typeof(Startup), typeof(ApplicationDbContext));
I also have a class called MappingProfile which resembles this:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<CfGroup, GroupModel>()
.IgnoreAllUnmapped();
CreateMap<GroupModel, CfGroup>()
.IgnoreAllUnmapped();
...
}
}
The IgnoreAllUnmapped was suggested by another SO question, and it looks like this:
public static IMappingExpression<TSource, TDest> IgnoreAllUnmapped<TSource, TDest>(
this IMappingExpression<TSource, TDest> expression)
{
expression.ForAllMembers(opt => opt.Ignore());
return expression;
}
My CfGroup looks like this:
[Table("Groups")]
public class CfGroup
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(100)]
[Required]
public string Name { get; set; }
[DataType(DataType.ImageUrl)]
[StringLength(250)]
public string ImageUrl { get; set; }
[Required]
public int FounderId { get; set; }
[ForeignKey("FounderId")]
public virtual CfUser Founder { get; set; }
[Required]
public bool IsActive { get; set; }
[Required]
public DateTime CreatedTimestampUtc { get; set; }
[StringLength(3000)]
public string About { get; set; }
public virtual ISet<UserGroupMember> Members { get; set; }
public virtual ISet<UserGroupManager> Managers { get; set; }
}
... and my GroupModel looks like this:
public class GroupModel
{
public int Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public string About { get; set; }
}
When calling my web api endpoint, I get a collection of CfGroup entities from the database just fine. I then run a command like so:
var dtos = entities.Select(_mapper.Map<GroupModel>);
...but my dtos are "empty"... IOW, the instance is created, but Id value is set to 0 and all the string values are set to null. That is definitely not what is in the entities variable.
I have also tried to explicitly convert it like so:
var dtos = entities.Select(x => _mapper.Map<GroupModel>(x));
and, postulating that it's an IEnumerable issue, tried making a List out of it:
var dtos = entities.Select(x => _mapper.Map<GroupModel>(x)).ToList();
...but no matter what, the DTO's remain instantiated, but with all properties set to their default values.
What am I doing wrong?
I suspect it may be because you are mapping inside your linq expression. try something like this:
var dtos = _mapper.Map<IEnumerable<GroupModel>>(entities.ToList());
So it turns out that the IgnoreAllUnmapped was not the correct way to go; in fact, the mapping seemed to work fine without it. I took out the extra "exception handling" code and it worked as I wanted it to. I was overthinking it.
I have the following class
public class Unit
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("unittype")]
public UnitType UnitType { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("language")]
public string Language { get; set; }
[JsonProperty("managers")]
public List<Manager> Managers { get; set; }
[JsonProperty("logoURL")]
public string LogoURL { get; set; }
[JsonProperty("naceCodeList")
public List<string> NaceCodeList { get; set; }
[JsonProperty("tags")]
public List<string> Tags { get; set; }
[JsonIgnore]
public string JoinedTags => string.Join(",", Tags);
[JsonProperty("respondents")]
public List<Respondent> Respondents { get; set; }
[JsonProperty("childUnits")]
public List<Unit> ChildUnits { get; set; }
public string Parent { get; set; }
}
I also have the following in the constructor of my BaseRepository
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Unit, Unit>();
});
My problem is that when I, in my repository, do Mapper.Map(unitToUpdate, unit); it empties all the List<whatever> properties.
Is this default behavior? Am I doing something wrong here?
For completeness, here is my UpdateUnit function
public void UpdateUnit(ClaimsPrincipal user, string orgId, Unit unitToUpdate)
{
var org = Get(user, orgId);
var unit = org.GetUnit(unitToUpdate.Id);
Mapper.Map(unitToUpdate, unit);
Update(user, org);
}
The stored object is another type containing a hierarchy of Unit objects representing a company. The org object is what is stored in the database, and the class representing the org object has it's own functions to load Unit objects inside itself.
I guess that your problem is that you need first to define the mappings for all collections from both objects and after that you need to define the mappings for objects.
Idea is to get from the bottom to the top (from the nested one to the Unit).
For example (taking into consideration only Respondents:
Mapper.CreateMap<RespondentDb, RespondentDto>();
Mapper.CreateMap<UnitDb, UnitDto>()
.ForMember(dest => dest.Respondents, opt => opt.MapFrom(src => src.Respondents);
Some references:
https://github.com/AutoMapper/AutoMapper/wiki/Nested-mappings
https://codereview.stackexchange.com/questions/70856/automapper-mapping-a-nested-collection-to-a-list (here we have an object that only contains a list, maybe it's good to know that too as you can encounter the same scenario)
EDIT: I originally worded this question very poorly, stating the problem was with JSON serialization. The problem actually happens when I'm converting from my base classes to my returned models using my custom mappings. I apologize for the confusion. :(
I'm using .NET Core 1.1.0, EF Core 1.1.0. I'm querying an interest and want to get its category from my DB. EF is querying the DB properly, no problems there. The issue is that the returned category has a collection with one interest, which has one parent category, which has a collection with one interest, etc. When I attempt to convert this from the base class to my return model, I'm getting a stack overflow because it's attempting to convert the infinite loop of objects. The only way I can get around this is to set that collection to null before I serialize the category.
Interest/category is an example, but this is happening with ALL of the entities I query. Some of them get very messy with the loops to set the relevant properties to null, such as posts/comments.
What is the best way to address this? Right now I'm using custom mappings that I wrote to convert between base classes and the returned models, but I'm open to using any other tools that may be helpful. (I know my custom mappings are the reason for the stack overflow, but surely there must be a more graceful way of handling this than setting everything to null before projecting from base class to model.)
Classes:
public class InterestCategory
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<Interest> Interests { get; set; }
}
public class Interest
{
public long Id { get; set; }
public string Name { get; set; }
public long InterestCategoryId { get; set; }
public InterestCategory InterestCategory { get; set; }
}
Models:
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public long? InterestCategoryId { get; set; }
}
Mapping functions:
public static InterestCategoryModel ToModel(this InterestCategory category)
{
var m = new InterestCategoryModel
{
Name = category.Name,
Description = category.Description
};
if (category.Interests != null)
m.Interests = category.Interests.Select(i => i.ToModel()).ToList();
return m;
}
public static InterestModel ToModel(this Interest interest)
{
var m = new InterestModel
{
Name = interest.Name,
Description = interest.Description
};
if (interest.InterestCategory != null)
m.InterestCategory = interest.InterestCategory.ToModel();
return m;
}
This is returned by the query. (Sorry, needed to censor some things.)
This is not .NET Core related! JSON.NET is doing the serialization.
To disable it globally, just add this during configuration in Startup
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
}));
edit:
Is it an option to remove the circular references form the model and have 2 distinct pair of models, depending on whether you want to show categories or interests?
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
Note that each of the models has a nested class for it's child objects, but they have their back references removed, so there would be no infinite reference during deserialization?
I am trying to solve seemingly trivial task. How to map string to a property of a collection? Bellow I show my current situation. I am trying to map the DeliveryType.UrlTemplate property of DeliveryNoteTableDto class to each PackageDto.UrlTemplate property. I am using AutoMapper. Currently I am getting error:
Custom configuration for members is only supported for top-level individual members on a type.
when performing the mapping. Any help?
classes
public class DeliveryNoteTableDto
{
public List<PackageDto> Packages { get; set; } = new List<PackageDto>();
public DeliveryTypeDto DeliveryType { get; set; }
}
public class PackageDto
{
public virtual string DeliveryKey { get; set; }
public virtual string UrlTemplate { get; set; }
}
public class DeliveryTypeDto
{
public virtual string Transporter { get; set; }
public virtual string UrlTemplate { get; set; }
}
mapping
map.ForMember(x => x.Packages.Select(p => p.UrlTemplate), opt => opt.MapFrom(x => x.DeliveryType.UrlTemplate));
In the below example, I'm just trying to get Test_Person_Name.FirstName to map to something (anything) in TestPersonFlattened. At this point, considering the amount of time I've sunk into this, I'm not too hung up on what the destination property name is..I just want it to work.
public class Test_Person
{
public Test_Person_Name Test_Person_PublicName { get; set; }
}
public class Test_Person_Name
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class TestPersonFlattened
{
public string Test_Person_PublicNameFirstName { get; set; } // What do I call this property?
}
AutoMapper.Mapper.CreateMap<Test_Person, TestPersonFlattened>();
AutoMapper.Mapper.AssertConfigurationIsValid();
It seems like Test_Person_PublicNameFirstName should work, but I get an exception on AssertConfigurationIsValid(). I've also tried TestPersonPublicNameFirstName, Test_Person_PublicName_FirstName as destination property names.
It'd be unfavorable to rename the source property name, just because the source library is used in many other projects. Also, a ForMember() call isn't ideal, but I'll do it if there's no other option.
One way to do it would be to simply leave out "Test_Person_" from the PublicNameFirstName property of your TestPersonFlattened class, and use RecognizePrefixes() to make it so that AutoMapper ignores "Test_Person_" when attempting to map property names.
The following code succeeds:
public partial class App : Application
{
public App()
{
Mapper.Initialize(cfg =>
{
cfg.RecognizePrefixes("Test_Person_");
cfg.CreateMap<Test_Person, TestPersonFlattened>();
});
Mapper.CreateMap<Test_Person, TestPersonFlattened>();
Mapper.AssertConfigurationIsValid();
}
}
public class Test_Person
{
public Test_Person_Name Test_Person_PublicName { get; set; }
}
public class Test_Person_Name
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class TestPersonFlattened
{
public string PublicNameFirstName { get; set; } // This is what I call this property!
}