Mapping entity to dto with interfaces - c#

I've my entity class I retrieve from database:
public class User{
public string Username {get; set;}
public List<IAddress> Addresses {get; set;}
}
public class Address: IAddress{
public string Line1 {get; set;}
public string Line2 {get; set;}
}
public class AddressExtended:Address, IAddress{
public string Line3 {get; set;}
public string Line4 {get; set;}
}
public interface IAddress{
}
I use Automapper to map this entity to the mirrored DTO:
public class UserDto{
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("addresses")]
public List<IAddressDto> Addresses { get; set; }
}
public class AddressDto: IAddressDto{
[JsonProperty("line1")]
public string Line1 { get; set; }
[JsonProperty("line2")]
public string Line2 { get; set; }
}
public class AddressExtendedDto:AddressDto, IAddressDto{
[JsonProperty("line3")]
public string Line3 { get; set; }
[JsonProperty("line4")]
public string Line4 { get; set; }
}
public interface IAddressDto{
}
Automapper configuration is the following:
CreateMap<IAddress, IAddressDto>();
CreateMap<Address, AddressDto>();
CreateMap<AddressExtended, AddressExtendedDto>();
The problem is that when I run my application, if in the entity I have 2 addresses and 1 addressExtended, in DTO the Addresses property () is mapped like this:
[
{Proxy<MyProject.Models.Dto.IAddressDto_MyProject_Version=1.0.0.0_Culture=neutral_PublicKeyToken=null>},
{Proxy<MyProject.Models.Dto.IAddressDto_MyProject_Version=1.0.0.0_Culture=neutral_PublicKeyToken=null>},
{Proxy<MyProject.Models.Dto.IAddressDto_MyProject_Version=1.0.0.0_Culture=neutral_PublicKeyToken=null>}
]
The Username property it is correctly valued.
What I miss?
UPDATE
I added a fiddler here:
https://dotnetfiddle.net/ZkUZgp

As per my knowledge, one approach solving the issue is using Construct using below code
cfg.CreateMap<Address, AddressDto>();
cfg.CreateMap<AddressExtended, AddressExtendedDto>();
cfg.CreateMap<IAddress, IAddressDto>().ConstructUsing((IAddress addressDto) =>
{
if (addressDto is AddressExtended) return Mapper.Map<AddressExtendedDto>(addressDto);
return Mapper.Map<AddressDto>(addressDto);
});
Edit 1:
Here is the final answer and it solves your problem
cfg.CreateMap<Address, AddressDto>();
cfg.CreateMap<AddressExtended, AddressExtendedDto>();
cfg.CreateMap<IAddress, IAddressDto>().ConstructUsing((addressDto, ctx) =>
{
var destination = Mapper.Instance.ConfigurationProvider.GetAllTypeMaps()
.First(t => t.SourceType == addressDto.GetType());
return ctx.Mapper.Map(addressDto, addressDto.GetType(), destination.DestinationType) as IAddressDto;
});
Instead of getting destination type using LINQ you can build a dictionary and get from it for faster execution.

Related

AutoMapper with nested objects and container class

I'm developing an API with .NET core and using AutoMapper.
all the API responses will be wrapped with a data element in the JSON response like below example
GET User
{
"data" {
"id" : 1,
"user_name": "abc"
"countryr" : {
"id" : 1348,
"code" : "USA"
}
}
}
So we have an entity for User and Country
public partial class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public virtual Country country { get; set; }
}
public partial class Country
{
public int Id { get; set; }
public string Code{ get; set; }
}
To map entities to DTO we have below reponseDTO
public class GetUserDTO {
public User data {get; set;} // To wrap reponse with data
}
public class UserDto {
public int id {get; set;}
public String user_name {get; set;}
public Country country {get; set;}
}
public class CountryDto {
public int id {get; set;}
public String code {get; set;}
}
As per my understanding , I should map the entity User to the UserDTO and Country entity to CountryDTO but what about GetUserDTO class itself? it basically contains other entities so the class itself cannot be mapped to anything it acts like a container.
So below what I did so far which is not correct
public class UserProfile : AutoMapper.Profile
{
public MappingProfile()
{
CreateMap<User, GetUserDTO>();
CreateMap<User, UserDto>()
.ForMember(userDto => userDto.user_name, map => map.MapFrom(user => user.FirstName))
CreateMap<Country, CountryDto>();
}
}
Json response
{
"data" : null
}
What to do for such situations?
You should not refer your entity classes in your DTO classes. You might want to change the DTO as below.
public class GetUserDTO {
public UserDto data {get; set;} // To wrap reponse with data
}
public class UserDto {
public int id {get; set;}
public string user_name {get; set;}
public CountryDto country {get; set;}
}
public class CountryDto {
public int id {get; set;}
public string code {get; set;}
}
And then in the Mapper profile, you need to explicitly map each property as cases are different(AutoMapper will map without explicity map if the names are exactly the same. In your example, there is a case difference)
And then remove the below line from the MapperProfile as there is no mapping from the User class to GetUserDTO class. This is the reason you are not getting any output.
CreateMap<User, GetUserDTO>();
While getting the data, you should create a new instance of GetUserDTO class and set the property "data" from the result of mappaing of the User object.
This will get you the output.

Entity Framework with extra data in navigation property

I'm using Entity Framework Code First.
Given these two classes,
public class Parts
{
public int Id {get; set; }
public string Name {get; set; }
}
public class SubParts
{
public int Id {get; set; }
public string Name {get; set; }
}
I've to define a navigation property to get this (ie):
Part1
SubPart1 - Order 1
SubPart5 - Order 2
SubPart2 - Order 3
Part2
SubPart5 - Order 1
SubPart3 - Order 2
SubPart6 - Order 3
SubPart2 - Order 4
... and so on.
My question is: How must I manage the property named Order?
Thanks in advance.
I found the solution here.
In my example, the classes I've define to implement this model are:
public class Parts
{
public int Id {get; set; }
public string Name {get; set; }
public virtual ICollection<PartsSubParts> SubParts {get; set; }
}
public class SubParts
{
public int Id {get; set; }
public string Name {get; set; }
}
public class PartsSubParts
{
public virtual Parts Part {get; set; }
public virtual SubParts SubPart {get; set;}
public int Order {get; set; }
}

Missing type map configuration or unsupported mapping error in Automapper

I am using an Automapper to map one model to second model which has some rows with the same name as the first model. I am getting this inner exception
Missing type map configuration or unsupported mapping.
Mapping types:
Bed -> Bed
Model1.Bed -> Model2.Bed
This is the condensed version of the code.
Model 1
public class Model1
{
public Guid Id { get; set; }
public string Name { get; set; }
public IEnumerable<Bed> Beds { get; set; }
public IEnumerable<Bed> Beds1 { get; set; }
public string Status {get; set;}
public string Notes {get; set;}
}
public class Model2
{
public Guid Id { get; set; }
public string Name { get; set; }
public IEnumerable<Bed> Beds { get; set; }
public IEnumerable<Bed> Beds1 { get; set; }
}
public class Bed
{
public Guid Id { get; set; }
public Guid TypeId { get; set; }
public string Type { get; set; }
public string Notes { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? LastUpdatedOn { get; set; }
}
Mapping :
MapperConfiguration config = null;
config = new MapperConfiguration(cfg => { cfg.CreateMap<Model1, Model2>(); });
IMapper mapper = config.CreateMapper();
var dest = mapper.Map<Model1, Model2>(update);
update is an instance of Model1. Am I missing something? Should I do something with regards to the Bed class mapping even though both reference the same method?
Also is there a way to copy data from one model to another in this scenario other than manually doing it or using Automapper?
EDIT 1:
I am sure the error is from that mapping. I removed it and all the other fields were fine and there was no exception. But I am not sure how I should do the Bed -> Bed binding.
EDIT 2:This link seems to have the same issue and they say it's a possible bug and This one seems to have the answer although I am not sure how I can adapt it to my situation.
It seems like auto mapper is having trouble determining how to map Bed. I would try the following:
config = new MapperConfiguration(cfg => {
cfg.CreateMap<Model1, Model2>();
cfg.CreateMap<Bed, Bed>();
});
Although it seems odd that would be necessary...

Entity framework 6 code first - Add entity with reference to another existing entity

Say I have the following models in my database:
public class LetterEntity
{
public int Id {get; set;}
public string Content {get; set;}
public List<Destination> Destinations {get; set;}
public virtual Folder Folder {get; set;}
public int FolderId {get; set;}
}
Now I want to add a new letter the client has made to my database:
public class SendLetterRequest
{
public string Content {get; set;}
public List<int> DestinationsIds {get; set;}
}
public void SaveLetterToDatabase(SendLetterRequest letter)
{
var letterEntity = new LetterEntity
{
Content = letter.Content;
FolderId = 1;
// How to insert the Destinations Ids in a way that I don't have to load all of those destinations to the context?
}
context.Set<LetterEntity>().Add(letterEntity);
context.SaveChanges();
}
I know that if a LetterEntity only had a single Destination object I could just set it's foreign key value and the insert would work (Just like I do with the FolderId).
How is it done when working with List of entities - how to tell EF that those Ids are already in the database, without fetching all of them to the context, so that it doesn't recreate them?
EDIT:
My Destination model -
public void Destination
{
// Manual key
public int Address {get; set;}
public string DestinationName {get; set;}
public string Information {get; set;}
}
Well, as you probably know, there are two ways to define many-to-many replationship in EF.
(1) Implicit link table
This is what you have used. You create explicitly only the two entitities, define the relation via navigation properties/and or model configuration and let EF maintain the so called "link" table. It's easy, but the downside is that you don't have access to that table, so the only way to add related items is to actually load the entities needed and add them to the navigation property collection.
(2) Explicit link table
Here you define explicitly the link entity and configure 2 one-to-many relations. This way you have access and can add related records w/o having the other entities loaded.
For instance, in your case it could be something like this:
Model:
public class LetterEntity
{
public int Id { get; set; }
// ....
public List<LetterDestinationLink> Links { get; set; }
}
public class Destination
{
public int Id { get; set; }
// ....
public List<LetterDestinationLink> Links { get; set; }
}
public class LetterDestinationLink
{
[Key]
[Column(Order = 0)]
public int LetterId { get; set; }
[Key]
[Column(Order = 1)]
public int DestinationId { get; set; }
public LetterEntity Letter { get; set; }
public Destination Destination { get; set; }
}
Context:
public class YourDbContext : DbContext
{
public DbSet<LetterEntity> LetterEntities { get; set; }
public DbSet<Destination> Destinations { get; set; }
public DbSet<LetterDestinationLink> LetterDestinationLinks { get; set; }
}
Use case:
List<int> destinationIds = ...;
var letterEntity = new LetterEntity { ... };
letterEntity.Links = destinationIds.Select(destinationId =>
new LetterDestinationLink { Letter = letterEntity, DestinationId = destinationId })
.ToList();
context.Set<LetterEntity>().Add(letterEntity);
context.SaveChanges();

Entity Framework Include Record with certain value in a Navigation Property

I am using Entity Framework 6 Code First and I'm configuring the mapping of my domain model with Fluent API. I don't see how to create a navigation properties for a Table which is a little tricky.
I have several objects which can make noise, I would like to record that noise in a NoiseRecord Table.
I need some kind of conditional mapping, something like that :
modelBuilder.Entity<NoiseRecord>().HasRequired(n=>n.Origine.OrigineType()=="Car").WithMany(c=>c.NoiseRecords);
That would be the mapping of the Car Navigation Property to avoid that, for example, it includes record related to Planes.
Here is my code
public interface INoisy
{
int ID {get; set;}
string OriginType()
...
//And other useful things not related to persistence
}
public class Car : INoisy
{
...
ICollection<NoiseRecord> NoiseRecords { get; set; }
string OrigineType()
{
return "Car";
}
}
public class Plane : INoisy
{
...
ICollection<NoiseRecord> NoiseRecords {get; set;}
string OrigineType()
{
return "Plane";
}
}
And a couple of other classes implement INoisy also.
Below is the NoiseRecord Table.
public class NoiseRecord
{
public int RecordID {get; set;}
public INoisy NoiseOrigine {get; set;}
public double NoiseMagnitude {get; set;}
}
I'm looking for a way to achieve that with Fluent API.
Thank you !
First of all, it is not possible to use interfaces as navigation properties. But you could use an abstract base class for your noise origins
public abstract class NoiseOrigin
{
public NoiseOrigin()
{
this.NoiseRecords = new Collection<NoiseRecord>();
}
public int Id { get; set; }
public ICollection<NoiseRecord> NoiseRecords { get; set; }
}
public class Car : NoiseOrigin {}
public class Plane : NoiseOrigin { }
public class NoiseRecord
{
public int Id { get; set; }
public int OriginId { get; set; }
public NoiseOrigin Origin { get; set; }
public double NoiseMagnitude { get; set; }
}
Your fluent API mapping whould look like this
public class NoiseModelContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Car>().Map(p => p.Requires("Type").HasValue("Car"));
modelBuilder.Entity<Plane>().Map(p => p.Requires("Type").HasValue("Plane"));
}
public DbSet<NoiseOrigin> NoiseOrigins { get; set; }
public DbSet<NoiseRecord> NoiseRecords { get; set; }
}
To get all car noise records your query will look like
using (var db = new NoiseModelContext()) {
var records = db.NoiseRecords.Where(p => p.Origin is Car);
// or like this - the result is the same.
var records2 = db.NoiseOrigins.OfType<Car>().SelectMany(p => p.NoiseRecords);
}

Categories

Resources