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.
Related
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.
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();
I have two entities with following relationShip (these entities are taken for example purpose only)
public class Entity
{
public long ID { get; set; }
}
public class Doctor : Entity
{
public string Name {get; set;}
public string sprcialization { get; set;}
public string Icollection<JrDoctor> childDoctors { get; set;}
}
public class JrDoctor : Entity
{
public long? DoctorId { get; set;}
public virtual Doctor Doctor { get; set;}
public long? JuniorDoctorId { get; set;}
[ForeignKey("JuniorDoctorId")]
public virtual Doctor JuniorDoctor { get; set;}
}
this relationship in entityframework is creating an extra column Doctor_Id in JrDoctor table. Why is it so? and how can I avoid it using data annotations.
Here is how EF works - if it sees navigation property (Doctor in your case), then EF understands that both entities are related to each other. Relation in database is defined by foreign keys. So EF generates foreign key with name PropertyName_KeyColumnOfRelatedEntity. That's why you see column Doctor_Id in JrDoctor table.
If you don't want default generated foreign key column, then you should tell EF what it should use instead. That is done via data annotations attributes or fluent configuration. I prefer latter one:
modelBuilder.Entity<JrDoctor>()
.HasOptional(jd => jd.Doctor)
.WithMany(d => d.childDoctors)
.HasForeignKey(jd => jd.DoctorId); // here you tell which column is FK
Data annotations require modification of entity classes. In your case you should add attribute which tells name of FK for navigation property, just as you did for JuniorDoctor:
public class JrDoctor : Entity
{
public long? DoctorId { get; set;}
[ForeignKey("DoctorId")]
public virtual Doctor Doctor { get; set;}
public long? JuniorDoctorId { get; set;}
[ForeignKey("JuniorDoctorId")]
public virtual Doctor JuniorDoctor { get; set;}
}
InverseProperty did the trick.
public class Entity
{
public long ID { get; set; }
}
public class Doctor : Entity
{
public string Name {get; set;}
public string sprcialization { get; set;}
[InverseProperty("Doctor")]
public string Icollection<JrDoctor> childDoctors { get; set;}
}
public class JrDoctor : Entity
{
public long? DoctorId { get; set;}
[ForeignKey("DoctorId")]
public virtual Doctor Doctor { get; set;}
public long? JuniorDoctorId { get; set;}
[ForeignKey("JuniorDoctorId")]
public virtual Doctor JuniorDoctor { get; set;}
}
I have a class named "City"
public class City
{
public int ID {get; set;}
public int StateID {get; set;}
public int CountryID{get; set;}
public string Name{get; set;}
.......
}
and i have an asp.net page named CityAdd.aspx, in this page i want to create a collection of city class that can be store in the viewstate.
Is it possible to make a Generic Collection Serializable?
do as below, add Serializable attribute
[Serializable]
public class City
{
}
I have an Account class which will be used to indicate who is responsible for a certain entity. There will be many entities where this will be used, so I don't want to pollute my Account class with all these collections
public class Account
{
public Guid Id{get; set;}
public Guid Name{get; set;}
...
public class EntityConfiguration : EntityConfigurationBase<Account>
{
public EntityConfiguration()
{
// I do not want these!
HasMany(a => a.As)
.WithOptional(x => x.Account)
.HasForeignKey(x =>x.AccountKey);
}
}
}
}
public class A
{
public Guid Id {get; set;}
public Account Account{get; set;}
// FK-Nav property
public Guid AccountKey{get;set;}
public class EntityConfiguration : EntityConfigurationBase<A>
{
public EntityConfiguration()
{
// what should go here to specify the association to Account?
????
}
}
}
public class B
{
public Guid Id {get; set;}
public Account Account{get; set;}
// FK-Nav property
public Guid AccountKey{get;set;}
public class EntityConfiguration : EntityConfigurationBase<B>
{
public EntityConfiguration()
{
// what should go here to specify the association to Account?
????
}
}
}
etc.
public class A
{
public Guid Id {get; set;}
public Account Account{get; set;}
// FK-Nav property
public Guid AccountKey{get;set;}
public class EntityConfiguration : EntityConfigurationBase<A>
{
public EntityConfiguration()
{
HasOptional(x => x.Account).HasMany().HasForeignKey(x=>x.AccountKey);
}
}
}