How to map Object to KeyValuePair<char, string> using AutoMapper? - c#

I'm trying to map my object model to a KeyValuePair<char, string> but my results are KeyValuePairs where Key = null and Value = null.
What's the correct way to do this?
Model:
public class Symbol
{
public int Id { get; set; }
public int TemplateId { get; set; }
public Template Template { get; set; }
public char Letter { get; set; }
public string ImgSource { get; set; }
}
Profile:
public class AutoMapping : Profile
{
public AutoMapping()
{
CreateMap<Symbol, KeyValuePair<object, object>>()
.ForMember(dest => dest.Key, opt => opt.MapFrom(src => src.Letter))
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.ImgSource))
.ReverseMap();
}
}
Attempt:
using (_dbContext)
{
var q = await _dbContext.Symbol
.Where(x => x.TemplateId == templateId)
.OrderBy(x => x.Letter)
.Select(x => _mapper.Map<KeyValuePair<char, string>>(x))
.ToListAsync();
//return _mapper.Map<List<KeyValuePair<char, string>>>(q);
return q;
}

Solved by using the following :
CreateMap<Symbol, KeyValuePair<char, string>>()
.ConstructUsing(sym => new KeyValuePair<char, string>(sym.Letter, sym.ImgSource));

Related

AutoMapper - How to map an object to list of objects

I wanna map Person to list of Client with AutoMapper:
and this is my models:
public class Person
{
public Guid Id { get; set;}
public string Name { get; set;}
public string Country { get; set;}
public string PhoneNumber { get; set;}
}
public class Member
{
public Guid Id { get; set;}
public string FullName { get; set; }
}
public class Client
{
public Member User { get; set; }
}
I tried to do it with AutoMapper but I couldn't:
CreateMap<Person, List<Client>>();
You need 4 mapping rules:
Map Person to Member.
Map Member to Client.
Map Person to Client.
Map Person to List<Client>.
CreateMap<Person, Member>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.Name));
CreateMap<Member, Client>()
.ForMember(dest => dest.User, opt => opt.MapFrom(src => src));
CreateMap<Person, Client>()
.ConvertUsing((src, dest, ctx) => new Client { User = ctx.Mapper.Map<Member>(src) });
CreateMap<Person, List<Client>>()
.ConvertUsing((src, dest, ctx) => new List<Client> { ctx.Mapper.Map<Client>(src) });
Demo # .NET Fiddle
You can map them using the following code:
CreateMap<Person, Client>()
.ForMember(dest => dest.User, opt => opt.MapFrom(src => new Member { Id = src.Id, FullName = src.Name }));
CreateMap<Person, List<Client>>()
.ConvertUsing(src => src.Select(x => new Client { User = new Member { Id = x.Id, FullName = x.Name } }).ToList());

C# AutoMapper issue with mapping Many-to-Many, wrong foreign key

I have faced with the strange issue with mapping model to entity with Many-to-Many relationship
I have the following entities and models:
using System;
using System.Diagnostics;
using System.Collections.Generic;
namespace AutoMapper.ReproducedExample
{
public class StoreEntity
{
public int Id { get; set; }
public string Name { get; set; }
public List<StoreProductEntity> Products { get; set; } = new List<StoreProductEntity>();
}
public class ProductEntity
{
public int Id { get; set; }
public string Name { get; set; }
public List<StoreProductEntity> Stores { get; set; } = new List<StoreProductEntity>();
}
public class StoreProductEntity
{
public int StoreId { get; set; }
public StoreEntity Store { get; set; }
public int ProductId { get; set; }
public ProductEntity Product { get; set; }
}
public class StoreModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ProductModel> Products { get; set; } = new List<ProductModel>();
}
public class ProductModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<StoreModel> Stores { get; set; } = new List<StoreModel>();
}
public class CustomProfile : Profile
{
public CustomProfile()
{
CreateMap<StoreModel, StoreEntity>()
.ForMember(d => d.Products,
opt => opt.MapFrom(s => s.Products))
.AfterMap((model, entity) =>
{
foreach (var entityProduct in entity.Products)
{
entityProduct.StoreId = entity.Id;
entityProduct.Store = entity;
}
});
CreateMap<StoreModel, StoreProductEntity>()
.ForMember(entity => entity.StoreId, opt => opt.MapFrom(model => model.Id))
.ForMember(entity => entity.Store, opt => opt.MapFrom(model => model))
.ForMember(entity => entity.ProductId, opt => opt.Ignore())
.ForMember(entity => entity.Product, opt => opt.Ignore());
CreateMap<ProductModel, ProductEntity>()
.ForMember(d => d.Stores,
opt => opt.MapFrom(s => s.Stores))
.AfterMap((model, entity) =>
{
foreach (var entityStore in entity.Stores)
{
entityStore.ProductId = entity.Id;
entityStore.Product = entity;
}
});
CreateMap<ProductModel, StoreProductEntity>()
.ForMember(entity => entity.StoreId, opt => opt.Ignore())
.ForMember(entity => entity.Store, opt => opt.Ignore())
.ForMember(entity => entity.ProductId, opt => opt.MapFrom(model => model.Id))
.ForMember(entity => entity.Product, opt => opt.MapFrom(model => model));
}
}
class Program
{
static void Main(string[] args)
{
var configuration = new MapperConfiguration(cfg =>
{
cfg.AddProfile<CustomProfile>();
});
#if DEBUG
configuration.AssertConfigurationIsValid();
#endif
var mapper = configuration.CreateMapper();
var store0 = new StoreModel()
{
Id = 1,
Name = "Store0",
};
var store1 = new StoreModel()
{
Id = 2,
Name = "Store1",
};
var product = new ProductModel()
{
Id = 1,
Name = "Product",
};
store1.Products.Add(product);
product.Stores.Add(store1);
store0.Products.Add(product);
product.Stores.Add(store0);
var store0Entity = mapper.Map<StoreEntity>(store0);
Debug.Assert(store0Entity.Products[0].Product.Stores[0].Store.Id ==
store0Entity.Products[0].Product.Stores[0].Store.Products[0].StoreId);
}
}
}
Mapping pass successfully, but by some reason some deep key is not mapped to the related StoreEntity
The following assertion is failed ...
Debug.Assert(store0Entity.Products[0].Product.Stores[0].Store.Id ==
store0Entity.Products[0].Product.Stores[0].Store.Products[0].StoreId);
Seems like by some reason in depth Store class AutoMapper uses wrong mapped List ... but I am not sure ...
I have figured out how to fixed this issue (thanks to answer on question Automapper creates 2 instances of one object)
For detecting Circular references I can add .PreserveReferences() method for mapping
Here is proper CustomProfile:
public class CustomProfile : Profile
{
public CustomProfile()
{
CreateMap<StoreModel, StoreEntity>()
.ForMember(d => d.Products,
opt => opt.MapFrom(s => s.Products))
.AfterMap((model, entity) =>
{
foreach (var entityProduct in entity.Products)
{
entityProduct.StoreId = entity.Id;
entityProduct.Store = entity;
}
})
.PreserveReferences();
CreateMap<StoreModel, StoreProductEntity>()
.ForMember(entity => entity.StoreId, opt => opt.MapFrom(model => model.Id))
.ForMember(entity => entity.Store, opt => opt.MapFrom(model => model))
.ForMember(entity => entity.ProductId, opt => opt.Ignore())
.ForMember(entity => entity.Product, opt => opt.Ignore());
CreateMap<ProductModel, ProductEntity>()
.ForMember(d => d.Stores,
opt => opt.MapFrom(s => s.Stores))
.AfterMap((model, entity) =>
{
foreach (var entityStore in entity.Stores)
{
entityStore.ProductId = entity.Id;
entityStore.Product = entity;
}
})
.PreserveReferences();
CreateMap<ProductModel, StoreProductEntity>()
.ForMember(entity => entity.StoreId, opt => opt.Ignore())
.ForMember(entity => entity.Store, opt => opt.Ignore())
.ForMember(entity => entity.ProductId, opt => opt.MapFrom(model => model.Id))
.ForMember(entity => entity.Product, opt => opt.MapFrom(model => model));
}
}
But actually I did not know if it is an issue or not, because according the documentation AutoMapper should detect Circular References automatically for AutoMapper version 10.0.0 and seems like this is an issue

How to OrderBy by related entity in EntityFramework

i am developing an API which returns records about representatives, everything works fine, i am getting the desired results, the problem starts when i want to sort by a related entity.
I am using Entity Framework to link the tables.
The following is a snippet of the DB diagram related to my piece of work.
I would like to order by Level in Tier table.
The following is my current working code:
var profiles = _context.Profile
.OrderBy(p => p.Person.FirstName)
.Include(p => p.Person)
.Include(p => p.Person.Address)
.Include(p => p.Person.Representative.RepresentativeTierHistory)
.ThenInclude(r => r.Tier)
.Skip(start)
.Take(limit);
var mappedProfiles = _mapper.Map<List<ShortLeaderProfile>>(profiles);
If someone could guide me on how to order the results by Tier.Level i would be really thankful. I have tried the following and it does not work..
Attempt:
.OrderBy(p => p.Person.Representative.RepresentativeTierHistory.OrderByDescending(t => t.Tier.Level))
This is my Mapping Code:
public ProfilesProfile()
{
MapAddressToLeaderProfile();
}
private void MapAddressToLeaderProfile()
{
CreateMap<Models.DataModels.Profile, ShortLeaderProfile>()
.ForMember(lp => lp.Id, opt => opt.MapFrom(p => p.Person.Id))
.ForMember(lp => lp.FirstName, opt => opt.MapFrom(p => p.Person.FirstName))
.ForMember(lp => lp.LastName, opt => opt.MapFrom(p => p.Person.LastName))
.ForMember(lp => lp.PreviousOccupation, opt => opt.MapFrom(p => p.PreviousOccupation))
.ForMember(lp => lp.Code,
opt => opt.MapFrom(p =>
ActiveTier(p.Person.Representative.RepresentativeTierHistory, DateTime.Now.Date)))
.ForMember(lp => lp.location, spt => spt.MapFrom(l => l));
CreateMap<Models.DataModels.Profile, Location>()
.ForMember(lp => lp.Name, opt => opt.MapFrom(p => p.Person.Address.AddressCityOrTown))
.ForMember(lp => lp.Latitude, opt => opt.MapFrom(p => p.Latitude))
.ForMember(lp => lp.Longitude, opt => opt.MapFrom(p => p.Longitude));
}
public static string ActiveTier(IEnumerable<RepresentativeTierHistory> representativeTierHistories, DateTime now) =>
representativeTierHistories?
.SingleOrDefault(x => x.StartDate <= now && x.EndDate > now)?
.Tier?
.Code;
}
This is my Profile Entity Class:
[Table(nameof(Profile), Schema = "common")]
[ExcludeFromCodeCoverage]
public class Profile
{
public int Id { get; set; }
public string PreviousOccupation { get; set; }
public string ShortDescription { get; set; }
public string LongDescription { get; set; }
[Column(TypeName = "numeric(10, 6)")]
public decimal? Longitude { get; set; }
[Column(TypeName = "numeric(10, 6)")]
public decimal? Latitude { get; set; }
public int DisplayOrder { get; set; }
public int PersonId { get; set; }
[ForeignKey(nameof(PersonId))]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDatetime { get; set; }
public List<ProfileSocialMedia> ProfileSocialMedias { get; set; }
public Person Person { get; set; }
My Class Model:
public class ShortLeaderProfile
{
public int Id;
public string FirstName;
public string LastName;
public string PreviousOccupation;
[CanBeNull] public string Code;
[CanBeNull] public Location location;
}
.OrderBy(p => p.Person.Representative.RepresentativeTierHistory.Max(t => t.Tier.Level))

Automapper - How to map to IEnumerable

I'm making Forum system, I have SubCategoryThreadsViewModel in which I'm trying to map LastComment and Date of last post for every thread. This is my code:
public class SubCategoryThreadsViewModel : IHaveCustomMappings
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<Thread> Threads { get; set; }
public ThreadInfoSubCategoryViewModel ThreadInfoSubCategoryViewModel { get; set; }
public void CreateMappings(IConfiguration configuration)
{
configuration.CreateMap<Thread, SubCategoryThreadsViewModel>()
.ForMember(m => m.Title, opt => opt.MapFrom(t => t.SubCategory.Title))
.ForMember(m => m.Description, opt => opt.MapFrom(t => t.SubCategory.Description))
.ForMember(m => m.Threads, opt => opt.MapFrom(t => t.SubCategory.Threads))
.ForMember(m => m.ThreadInfoSubCategoryViewModel, opt => opt.MapFrom(t => new ThreadInfoSubCategoryViewModel()
{
LastCommentBy = t.Posts.Select(a => a.Author.UserName),
DateOfLastPost = t.Posts.Select(a => a.CreatedOn.ToString()),
}))
.ReverseMap();
}
The code
.ForMember(m => m.ThreadInfoSubCategoryViewModel, opt => opt.MapFrom(t => new ThreadInfoSubCategoryViewModel()
{
LastCommentBy = t.Posts.Select(a => a.Author.UserName),
DateOfLastPost = t.Posts.Select(a => a.CreatedOn.ToString()),
}))
is working but only when property ThreadInfoSubCategoryViewModel is not Ienumerable as in the code above, and inside are two IEnumerable strings.
public class ThreadInfoSubCategoryViewModel
{
public IEnumerable<string> LastCommentBy { get; set; }
public IEnumerable<string> DateOfLastPost { get; set; }
}
This works, but I want ThreadInfoSubCategoryViewModel to be Ienumerable, and in the class properties to be string for easy foreach.
I have tried to make it IEnumerable, but with current automapper code it doesn't work.
You need to manually map the member to an IEnumerable<ThreadInfoSubCategoryViewModel> rather than a single object.
I assume each Post in t.Posts represents one ThreadInfoSubCategoryViewModel, so a simple Select() should do it:
public IEnumerable<ThreadInfoSubCategoryViewModel> ThreadInfoSubCategoryViewModel { get; set; }
...
.ForMember(m => m.ThreadInfoSubCategoryViewModel, opt => opt.MapFrom(t =>
t.Posts.Select(p => new ThreadInfoSubCategoryViewModel()
{
LastCommentBy = p.Author.UserName,
DateOfLastPost = p.CreatedOn.ToString()
})
))

AutoMapper settings properties to null on destination object

I have something like this:
public class DomainEntity
{
public string Name { get; set; }
public string Street { get; set; }
public IEnumerable<DomainOtherEntity> OtherEntities { get; set; }
public IEnumerable<DomainAnotherEntity> AnotherEntities { get; set; }
}
public class ApiEntity
{
public string Name { get; set; }
public string Street { get; set; }
public int OtherEntitiesCount { get; set; }
}
And following mapper configuration:
Mapper.Configuration.AllowNullCollections = true;
Mapper.CreateMap<DomainEntity, ApiEntity>().
ForSourceMember(e => e.OtherEntities, opt => opt.Ignore()).
ForSourceMember(e => e.AntherEntities, opt => opt.Ignore()).
ForMember(e => e.OtherEntitiesCount, opt => opt.MapFrom(src => src.OtherEntities.Count()));
Mapper.CreateMap<ApiEntity, DomainEntity>().
ForSourceMember(e => e.OtherEntitiesCount, opt => opt.Ignore()).
ForMember(e => e.OtherEntities, opt => opt.Ignore()).
ForMember(e => e.AnotherEntities, opt => opt.Ignore());
To get the ApiEntity from the DomainEntity I'm using var apiEntity = Mapper.Map<DomainEntity, ApiEntity>(myDomainEntity);
To get the merged DomainEntity from an ApiEntity I'm using var domainEntity = Mapper.Map(myApiEntity, myDomainEntity);
But when using this, the properties OtherEntities and AnotherEntities are set to null - even when they had values before calling the mapping from myApiEntity to myDomainEntity. How can I avoid this so they really merge and not just replacing values?
Thanks for any help.
I think you're looking for UseDestinationValue instead of Ignore:
Mapper.CreateMap<ApiEntity, DomainEntity>().
ForSourceMember(e => e.OtherEntitiesCount, opt => opt.UseDestinationValue()).
ForMember(e => e.OtherEntities, opt => opt.UseDestinationValue()).
ForMember(e => e.AnotherEntities, opt => opt.UseDestinationValue());

Categories