Map components of DTO which are DTOs as well - c#

This is my class which holds database data:
public partial class PermissionGroup
{
public int Id { get; set; }
public string Name { get; set; }
// other database properties
public virtual ICollection<GroupActionPermission> GroupActionPermissions { get; set; }
}
And that's my dto's:
public class PermissionGroupDTO
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<GroupActionPermissionDTO> ActionPermissions { get; set; }
}
public class GroupActionPermissionDTO
{
public int Id { get; set; }
public int GroupId { get; set; }
public int PermissionActionId { get; set; }
public PermissionGroupDTO Group { get; set; }
}
Now, I am making mapping:
public IEnumerable<PermissionGroupDTO> GetGroups()
{
return OnConnect<IEnumerable<PermissionGroupDTO>>(db =>
{
return db.PermissionGroups
.Include(i => i.GroupActionPermissions)
.ProjectTo<PermissionGroupDTO>()
.ToList();
});
}
And I am getting collection of PermissionGroupDTO which should contains collection of GroupActionPermissionDTO, but that collection stays null. Is there something wrong with my code? I am afraid that automapper can map collections from foreign keys.
Also, thats my automapper initializer:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<PermissionGroup, PermissionGroupDTO>();
cfg.CreateMap<GroupActionPermission, GroupActionPermissionDTO>();
});

I believe the reason is desribed here http://docs.automapper.org/en/stable/Queryable-Extensions.html
Note that for this feature to work, all type conversions must be explicitly handled in your Mapping.
So that means you should manually configure the mapping:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<PermissionGroup, PermissionGroupDTO>()
.ForMember(dto => dto.ActionPermissions , conf => conf.MapFrom(ol => ol.GroupActionPermissions )));;
cfg.CreateMap<GroupActionPermission, GroupActionPermissionDTO>();
});
BTW, note that fields are named differently: GroupActionPermissions vs. ActionPermissions. This is also the reason why automapper doesn't map it automatically and then you should use the manual configuration I wrote.

Related

Automapper, map a property that is on the many to many table

I'm working on an .NET 5 API.
I have to reply to a get call with a Json that serializes the UnitDto class and inside it the list of all the InstDto class but I need a property that resides on the UnitInst object (the table many to many)
My classes:
public class Unit
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<UnitInst> UnitInsts { get; set; }
}
public class Inst
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<UnitInst> UnitInsts { get; set; }
}
public class UnitInst
{
public long Id { get; set; }
public long UnitId { get; set; }
public virtual Unit Unit { get; set; }
public long InstId { get; set; }
public virtual Inst Inst { get; set; }
public string IPv4 { get; set; } // the property that is important
}
My dto's
public class UnitDto
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<InstDTO> Insts { get; set; }
}
public class InstDTO
{
public long Id { get; set; }
public string Name { get; set; }
public string IPv4 { get; set; } // I need serialize this property in my response json
}
I map in this way and it's ok but i can't retrive IPv4 property from UnitInst class (the many to many table)
CreateMap<Unit, UnitDto>()
.ForMember(dto => dto.Insts, opt => opt.MapFrom(x => x.UnitInsts.Select(y => y.Inst).ToList()))
.PreserveReferences();
How can I solve?
Normally you would create 2 maps (Unit -> UnitDto and Inst -> InstDto) and use the Select trick you've shown. But that is applicable only when the join entity has no additional data, which is not the case here.
So you need to map the join entity collection directly:
CreateMap<Unit, UnitDto>()
.ForMember(dst => dst.Insts, opt => opt.MapFrom(src => src.UnitInsts)); // <-- no Select
and create additional map UnitInst -> InstDto:
cfg.CreateMap<UnitInst, InstDTO>()
.IncludeMembers(src => src.Inst) // needs `Inst` -> `InstDTO` map
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Inst.Id));
Here AutoMapper IncludeMembers is used to map the Inst members specified by the regular Inst -> InstDTO map, and the target Id property is mapped explicitly because both source and "included" objects have a property with the same name, in which case the source have priority, but you want Id to be Inst.Id or InstId.
Finally the Inst -> InstDTO map:
CreateMap<Inst, InstDTO>()
.ForMember(dst => dst.IPv4, opt => opt.Ignore());

Many to many relationship mapping in EF Core

I have a problem with many to many relationship in EF core.
I have the following model classes:
public class Meal
{
public int Id { get; set; }
[Required]
public int Insulin { get; set; }
public MealType Type { get; set; }
public ICollection<MealFood> MealFoods { get; set; }
public Meal()
{
MealFoods = new Collection<MealFood>();
}
}
public class Food
{
public int Id { get; set; }
[StringLength(255)]
public string Name { get; set; }
[Required]
public int Carbohydrates { get; set; }
public ICollection<MealFood> MealFoods { get; set; }
public Food()
{
MealFoods = new Collection<MealFood>();
}
}
public class MealFood
{
public int MealId { get; set; }
public Meal Meal { get; set; }
public int FoodId { get; set; }
public Food Food { get; set; }
}
I have the following API resource class:
public class MealResource
{
public int Id { get; set; }
public int Insulin { get; set; }
public MealType Type { get; set; }
public ICollection<FoodResource> Foods { get; set; }
public MealResource()
{
Foods = new Collection<FoodResource>();
}
}
I have done the mapping in my DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MealFood>().HasKey(mf => new { mf.MealId, mf.FoodId });
modelBuilder.Entity<MealFood>().HasOne(mf => mf.Meal).WithMany(m => m.MealFoods).HasForeignKey(mf => mf.MealId);
modelBuilder.Entity<MealFood>().HasOne(mf => mf.Food).WithMany(f => f.MealFoods).HasForeignKey(mf => mf.FoodId);
}
I've got a problem with this call:
var meals = await context.Meals.Include(m => m.MealFoods).ToListAsync();
This returns almost everything I need, except the navigation properties from MealFoods
The reason why I want those properties, because I want to do the following mapping:
CreateMap<Meal, MealResource>().ForMember(mr => mr.Foods, opt => opt.MapFrom(x => x.MealFoods.Select(y => y.Food).ToList()));
I have already found this:
Automapper many to many mapping
but (maybe I don't get something) this doesn't work because the property called Food in MealFood is null.
I hope I didn't explain too complex.
When you include navigation property, EF Core automatically fills the inverse navigation property, e.g. including Meal.MealFoods will automatically fill MealFood.Meal, including Food.MealFoods will automatically populate MealFood.Food etc. In order to populate other navigation properties you need to use additional ThenInclude. E.g.
var meals = await context.Meals
.Include(m => m.MealFoods)
.ThenInclude(mf => mf.Food) // <--
.ToListAsync();
or
var foods = await context.Foods
.Include(f => f.MealFoods)
.ThenInclude(mf => mf.Meal) // <--
.ToListAsync();

AutoMapper one source object to multiple destination objects

I've got a source model defined as
public class SourceRoot
{
...
public Organisation Organisation { get; set; }
...
}
public class Organisation
{
public long? Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
public double SupplementaryAmount { get; set; }
public decimal BaseConversionRate { get; set; }
}
and a destination defined as:
public class DestinationRoot
{
...
public Organisation Organisation { get; set; }
public ContributesTo ContributesTo { get; set; }
}
public class Organisation
{
public long? Id { get; set; }
public string Name { get; set; }
}
public class ContributesTo
{
public string Currency { get; set; }
public double SupplementaryAmount { get; set; }
public decimal BaseConversionRate { get; set; }
}
I want to map from the SourceRoot to the DestinationRoot add copy from the source Organisation to the destination Organisation AND ContributesTo.
I have the following configuration for AutoMapper:
public static class AutoMapperConfig
{
public static MapperConfiguration RegisterMappings()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MyProfile>();
});
return config;
}
}
public class MyProfile : Profile
{
protected override void Configure()
{
this.CreateMap<SourceRoot, DestinationRoot>();
this.CreateMap<Source.Organisation, Destination.Organisation>();
this.CreateMap<Source.Organisation, Destination.ContributesTo>();
}
}
Using this current profile the Organisation gets mapped but the ContributesTo comes out as null.
Note that I'm using version 4.2 of AutoMapper where the static methods have been deprecated so trying to steer away from that. Normally I would do:
Mapper.CreateMap<SourceRoot, DestinationRoot>()
.ForMember(d => d.ContributesTo, opt => opt.MapFrom( s=> Mapper.Map<ContributesTo>(s.Organisation)));
But this is not advised anymore (referencing the static methods). Is there an alternative way of doing this?
Thanks
Just add mapping for ContributesTo destination member:
protected override void Configure ()
{
CreateMap<Source.SourceRoot, Destination.DestinationRoot>()
.ForMember(d => d.ContributesTo, opt => opt.MapFrom(s => s.Organisation));
CreateMap<Source.Organisation, Destination.Organisation>();
CreateMap<Source.Organisation, Destination.ContributesTo>();
}
Otherwise Automapper finds that both source and destinaton roots have property Organisation and it maps only this property. Automapper cannot understand that it should use one property of source to map several properties of destination (which do not match by name). Note that you don't need to specify mapping for Organisation member, because it matches property name in destination object.

Automapper mapping of complex objects

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

Why is The navigation property '' declared on type '' has been configured with conflicting multiplicities. error show up?

I have an Asp_Users table and a Resource tables. In the Entity Framework world, I have a Asp_Users POCO and a Resource POCO. Moreover, the Resource POCO is abstract and is part of a Table-per-Hierarchy model. The Table-per-Hierarchy model has the abstract Resource POCO and several Concrete POCOs like ILCResource POCO and SectionResource POCO. There is a one-to-many (1 to 0…*) relationship from Asp_Users POCO (one-side) to Resource POCO (many-side).
Here's the relevant part of my aspnet_Users POCO:
public partial class aspnet_Users
{
public aspnet_Users() { }
public virtual System.Guid ApplicationId { get; set; }
public virtual System.Guid UserId { get; set; }
public virtual string UserName { get; set; }
public virtual string LoweredUserName { get; set; }
public virtual string MobileAlias { get; set; }
public virtual bool IsAnonymous { get; set; }
public virtual System.DateTime LastActivityDate { get; set; }
public virtual ICollection<Resource> associatedResources { get; set; }
}
Here is my mapping configuration for Asp_Users
public class Aspnet_UsersMap : EntityTypeConfiguration<PerlsData.Domain.aspnet_Users>
{
public Aspnet_UsersMap()
{
this.ToTable("aspnet_Users", schemaName: "dbo");
this.HasKey(u => u.UserId);
this.Property(u => u.UserId)
.HasColumnName("UserId")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.HasOptional(u => u.associatedResources);
}
}
Here's the relevant part of my abstract Resource POCO class:
public abstract class Resource
{
public Resource(){
// associatedPerspectives = new HashSet<Perspective>();
}
public virtual System.Guid ResourceDatabaseID { get; set; }
public virtual string ResourceName { get; set; }
public virtual string DescriptionOfResource { get; set; }
public virtual System.Guid UserId { get; set; }
public virtual Nullable<System.Guid> DepartmentDatabaseID { get; set; }
public virtual string ResourceStatus { get; set; }
public virtual Nullable<short> isRemoved { get; set; }
public virtual Department Department { get; set; }
public virtual System.Guid UserId { get; set; }
public virtual aspnet_Users aspnet_Users { get; set; }
public virtual ICollection<ResourceOverToILCResourcesBridge> associatedResourceOverToILCResourcesBridgeEntry { get; set; }
}
Here is my mapping configuration for Resource:
public class ResourceMap : EntityTypeConfiguration<Resource>
{
public ResourceMap()
{
this.ToTable("Resources", schemaName: "dbo");
this.Property(r => r.ResourceDatabaseID)
.HasColumnName("ResourceDatabaseID");
this.HasKey(r => r.ResourceDatabaseID);
this.Property(x => x.ResourceDatabaseID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// .StoreGeneratedPattern = StoreGeneratedPattern.Identity;
this.Property(r => r.ResourceName)
.HasColumnName("ResourceName");
this.Map<PerlsData.Domain.OtherItem>(m => m.Requires("discriminator").HasValue("otheritems"))
.Map<PerlsData.Domain.Audioitem>(m => m.Requires("discriminator").HasValue("audioitems"))
.Map<PerlsData.Domain.Imageitem>(m => m.Requires("discriminator").HasValue("imageitems"))
.Map<PerlsData.Domain.Videoitem>(m => m.Requires("discriminator").HasValue("videoitems"))
.Map<PerlsData.Domain.UriItem>(m => m.Requires("discriminator").HasValue("uriitems"))
.Map<PerlsData.Domain.Documentitem>(m => m.Requires("discriminator").HasValue("documentitems"))
.Map<PerlsData.Domain.DatabaseFileItem>(m => m.Requires("discriminator").HasValue("databasefileitems"));
this.HasOptional(res => res.associatedResourceOverToILCResourcesBridgeEntry);
this.HasRequired(res => res.aspnet_Users)
.WithMany(u => u.associatedResources)
.HasForeignKey(res => res.UserId);
}
}
Could you please tell me why I am getting the following error?
The navigation property 'associatedResources' declared on type
'PerlsData.Domain.aspnet_Users' has been configured with conflicting
multiplicities.
Please Explain Why it's still NULL after I created the mapping in the POCO class.
this.HasOptional(u => u.associatedResources);
That's wrong. HasOptional means 0..1.
You want HasMany().
In fact, you can get rid of that line entirely; EF can figure it out from the property.
I need to study more about the IQueryable and/or IEnumerable collections along with the lambda expression queries that are used to retrieve data, and place them in the aforementioned IQueryable and/or IEnumerable.
My problem was that I was trying to retrieve the associations of a particular POCO within a loop that iterates over the IQueryable and/or IEnumerable collections.
Here is the Faulty code (we are accessing association while we iterate over IQueryable):
resourcestempIQueryable = context.Resources;
foreach (PerlsData.Domain.Resource tempRes in resourcestempIQueryable)
{
string[] resourceRow = { tempRes.ResourceName,
tempRes.DescriptionOfResource,
tempRes.UploadDate.ToString(),
tempRes.Iconpath,
tempRes.aspnet_Users.UserName, // Error
tempRes.ResourceDatabaseID.ToString() };
tempDataTableForResources.Rows.Add(resourceRow);
}
Here is the proper code that should resolve the issue:
using (PerlsData.Context context = new PerlsData.Context())
{
context.Configuration.LazyLoadingEnabled = true;
context.Configuration.ProxyCreationEnabled = true;
resourcesIEnumerable = context.Resources.ToList<PerlsData.Domain.Resource>();
var entityValuesWithFieldsToShowInAllResourcesPage = context.Resources.Select(res => new { res.ResourceName, res.DescriptionOfResource, res.UploadDate, res.Iconpath, res.aspnet_Users.UserName, res.ResourceDatabaseID });
foreach (var varForIteratingOverEntityValuesWithFieldsToShowInAllResourcesPage in entityValuesWithFieldsToShowInAllResourcesPage)
{
string[] resourceRow = {
varForIteratingOverEntityValuesWithFieldsToShowInAllResourcesPage.ResourceName,
varForIteratingOverEntityValuesWithFieldsToShowInAllResourcesPage.DescriptionOfResource,
varForIteratingOverEntityValuesWithFieldsToShowInAllResourcesPage.UploadDate.ToString(),
varForIteratingOverEntityValuesWithFieldsToShowInAllResourcesPage.Iconpath,
varForIteratingOverEntityValuesWithFieldsToShowInAllResourcesPage.UserName,
varForIteratingOverEntityValuesWithFieldsToShowInAllResourcesPage.ResourceDatabaseID.ToString()
};
tempDataTableForResources.Rows.Add(resourceRow);
}
}

Categories