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)
Related
I'm implementing an WebAPI using dotnet core (2) and EF Core. The application uses UnitOfWork/Repository-pattern. I've come to a point where I need to implement a "Many-To-Many"-relation but I'm having some trouble. This is what I've got sofar:
Entities:
public class Team : DataEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TeamId { get; set; }
public int OrganizationID { get; set; }
public string Name { get; set; }
public string URL { get; set; }
public virtual ICollection<Season> Seasons { get; set; }
public ICollection<TeamMember> TeamMember { get; set; }
}
public class Member : DataEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MemberId { get; set; }
public string Name { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MobilePhone { get; set; }
public string Email { get; set; }
public int RelatedTo { get; set; }
public int TeamRole { get; set; }
public ICollection<TeamMember> TeamMember { get; set; }
}
public class TeamMember
{
public int TeamId { get; set; }
public Team Team { get; set; }
public int MemberId { get; set; }
public Member Member { get; set; }
}
In my DbContext-class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TeamMember>()
.HasKey(t => new { t.TeamId, t.MemberId });
modelBuilder.Entity<TeamMember>()
.HasOne(tm => tm.Team)
.WithMany(t => t.TeamMember)
.HasForeignKey(tm => tm.TeamId);
modelBuilder.Entity<TeamMember>()
.HasOne(tm => tm.Member)
.WithMany(t => t.TeamMember)
.HasForeignKey(tm => tm.MemberId);
}
On my repository I have en extension for IncludeAll:
public static IQueryable<Team> IncludeAll(this IGenericRepository<Team> repository)
{
return repository
.AsQueryable()
.Include(s => s.Seasons)
.Include(tm => tm.TeamMember);
}
Everything builds as expected but when trying to invoke the controller-action that's fetching a Team (which I expect to include all members) or a Member (which I expect to include all the members teams - code not included above). SwaggerUI returns: TypeError: Failed to fetch. If I try to invoke the controller-action directly in chrome (http://localhost/api/Team/1) i get an incomplete result, but never the less...a result :) :
{
"value":
{
"teamId":1,
"organizationID":1,
"name":"Team1",
"url":"http://www.team1.com",
"seasons":[
{
"id":1,
"teamID":1,
"name":"PK4",
"description":null,
"startDate":"2017-09-01T00:00:00",
"endDate":"2018-12-31T00:00:00",
"created":"2017-12-01T00:00:00",
"updated":"2017-12-27T00:00:00",
"createdBy":"magnus",
"updatedBy":"magnus"
}],
"teamMember":[
{
"teamId":1
Am I missing something obvious?
Might be related to some circular reference issue?!
your teamMember property has an Team property which again has a teamMember properties and so on... When trying to serialize, a never ending loop would be created.
Maybe you can set the teamMember.Team property to null. I think you would also have to set the teamMember.Member.teamMember property to null.
You have a reference loop in your data structure (team -> team member -> team), and the JSON encoder can't deal with this.
I can think of two ways to solve this:
Configure the JSON encoder to ignore reference loops
In Startup.cs, add this:
services
.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
From the documentation:
ReferenceLoopHandling.Ignore: Json.NET will ignore objects in reference loops and not serialize them. The first time an object is encountered it will be serialized as usual but if the object is encountered as a child object of itself the serializer will skip serializing it.
Add the [JsonIgnore] attribute to the property you want to ignore
For example:
public class TeamMember
{
public int TeamId { get; set; }
[JsonIgnore]
public Team Team { get; set; }
}
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));
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.
I am using Automapper to map my Model objects to DTO. In DTO the primary key should be replaced with the corresponding object. For this purpose I used the code below:
// Model class
public class SubDepartment
{
public long Id { get; set; }
public string Name { get; set; }
public long? DepartmentId { get; set; }
public DateTime LastUpdated { get; set; }
}
// DTO class
public class SubDepartmentDTO
{
public long Id { get; set; }
public string Name { get; set; }
public Department Department { get; set; }
public long EventCount { get; set; }
}
// Mapping code
Mapper.CreateMap<Models.Event.SubDepartment, DTO.SubDepartment>().ForMember(dto => dto.Department,
map => map.MapFrom(sd => Mapper.Map<Department, DTO.Department>(_departmentRepository.GetById(sd.DepartmentId.Value))));
But when I map from SubDepartment to SubDepartmentDTO in my controller, the 'Department' object is always null. I tried replacing the _departmentRepository.GetById(sd.DepartmentId.Value) code with a hardcoded Department object and it is working good. I also verified there is a corresponding Department exist in the database for the primary key. Can anyone point out what I am doing wrong ?
Try it with this code
Mapper.CreateMap<Models.Event.SubDepartment, DTO.SubDepartment>().ForMember(dto => dto.Department,map => map.MapFrom(sd => _departmentRepository.GetById(sd.DepartmentId.Value)));
if it doesn't work you could try Custom value resolver
Models.Event.SubDepartment, DTO.SubDepartment>().ForMember(dto => dto.Department, map => map.ResolveUsing<DepartmentResolver>());
public class DepartmentResolver: ValueResolver<Models.Event.SubDepartment,DTO.SubDepartment>
{
Reporsitory _departmentRepository;
protected override DTO.SubDepartment ResolveCore(Models.Event.SubDepartment source)
{
return _departmentRepository.GetById(source.DepartmentId.Value);
}
}