C# How to map inner property object to outer class with AutoMapper? - c#

I have 3 classes:
public class CountryModel
{
public int Id { get; set; }
public string Title { get; set; }
}
public class CountryDTO
{
public int Id { get; set; }
public string Title { get; set; }
}
public class BaseCountryDTO
{
public CountryDTO Country {get; set};
}
I need to map CountryDTO to CountryModel, but through BaseCountryDTO class.
I know that I can do it like this:
CreateMap<BaseCountryDTO, CountryModel>()
.ForMember(model => model.Id, o => o.MapFrom(dto => dto.Country.Id))
.ForMember(model => model.Title, o => o.MapFrom(dto => dto.Country.Title));
But I want to do it clear, something like this:
// This is not working code, just my imagination :)
CreateMap<BaseCountryDTO, CountryModel>()
.ForMember(model => model, dto => dto.Country));
Because in model can be more than 2 properties. Is there way to do it?

#LucianBargaoanu Helped me with the link https://docs.automapper.org/en/latest/Flattening.html#includemembers
It solved my problem.
The solution looks like:
CreateMap<BaseCountryDTO, CountryModel>().IncludeMembers(s => s.Country);
CreateMap<CountryDTO, CountryModel>();
So the thing is we have to create a map of base class to our model with include of what we are really want to map. Then we should create a map of class which we are really need to map.

If the properties in CountryModel and CountryDTO have same names/types, then you can configure the mapping simply as -
CreateMap<CountryDTO, CountryModel>();
You can test the mapping as -
CountryDTO dto = new CountryDTO { Id = 4, Title = "Something" };
CountryModel model = Mapper.Map<CountryModel>(dto);
It will automatically map the properties from CountryDTO to CountryModel, no matter how many they are. You don't have to manually configure mapping for any property, or go through another class like BaseCountryDTO.

Related

Automapper custom inline mapping

I have two classes
public class SourceClass
{
public Guid Id { get; set; }
public Guid Provider { get; set; }
}
public class DestinationClass
{
public Guid Id { get; set; }
public Guid Provider { get; set; }
public Guid CustomerId {get; set;}
}
I've initialized my mapping by using the following code
CreateMap<SourceClass, DestinationClass>();
And then in my controller, I have :
Mapper.Map<List<DestinationClass>>(requests)
where "requests" is a List of SourceClass objects being passed in to my controller.
Now, this code works and the mapping works as configured. However, I also get passed a CustomerId and want to set it in the DestinationClass accordingly.
Is there any way to do this while the mapping is occuring, so that I don't have to write an additional loop to set CustomerId in every object in the list?
You can pass additional parameter by passing key-value to mapper (as suggested by #LucianBargaoanu). The custom value resolver and map execution can be implemented as:
// Configuration
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceClass, DestinationClass>()
.ForMember(dest => dest.CustomerId, opt =>
opt.MapFrom((src, dest, destMember, context) =>
context.Items["CustomerId"]));
});
var mapper = configuration.CreateMapper();
//Sample source class
var sourceClass = new SourceClass { Id = Guid.NewGuid(), Provider = Guid.NewGuid() };
var destClass = mapper.Map<SourceClass, DestinationClass>(sourceClass,
opt => opt.Items["CustomerId"] = "96b4b6e6-7937-4579-ba01-4a051bc0b93b");
The CustomerId member of destClass object is populated with passed GUID.
Note:SourceClass and DestinationClass definition are taken from OP.

How to keep original value of a source property in Automapper mapping?

The input view model:
public class FacilityInputModel
{
public int Id { get; set; }
public string Name { get; set; }
}
The domain model:
public class FacilityInputModel
{
public int Id { get; set; }
public string Name { get; set; }
public string OriginalName { get; set; }
}
I am to allow users to change the name of a facility but still keep its original name.
Say facility is (I am writing json just for convenience)
{id:1, name='Facility1', originalName='Facility1'}
when created.
I am to change the name by posting a FacilityInputModel.
{id:1, name='Facility2'}
In C# code to update the entity:
var entity = _repository.Find(id);
_repository.Detach(entity);
entity = Mapper.Map<Facility>(model);
_repository.Update(entity);
_repository.Save();
The entity I get before Mapper.Map
{id:1, name='Facility1', originalName='Facility1'}
But after the mapping, the entity is
{id:1, name='Facility2', originalName=null}
instead of
{id:1, name='Facility2', originalName='Facility1'}
In my mapping, I tried to Ignore the OriginalName
CreateMap<Facility, FacilityInputModel>()
.ReverseMap()
.ForMember(x => x.OriginalName, opt => opt.Ignore());
But it never worked. Also tried
.ForMember(x => x.NameInWebAdmin, opt => opt.DoNotUseDestinationValue());
Still won't work.
So the question is how to avoid existing values from being wiped away in the mapping. And get an after-mapping entity:
{id:1, name='Facility2', originalName='Facility1'}
You're getting a completely new object when you call entity = Mapper.Map<Facility>(model);. Try using Mapper.Map<Source, Destination>(source, destination) to map to an existing one.

One-Many via NHibernate Loquacious mapping by code or xml

I want to implement a one-to-many relationship between a person and car, and have CRUD operations on both person and car. Brief CRUD and relationships:
Update
A person has many cars
CRUD operations on both person and car via person object.
Deleting a person will delete all s/his cars
Ability to perform CRUD operation on someone's cars, either via person object or car object.
Is it possible via ORM, in particular NHibernate?
Classes like below:
public class PersonSet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ISet<CarSet> Cars { get; set; }
}
public class CarSet
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual PersonSet Person { get; set; }
}
Mapping is shown below:
public class PersonSetMap : ClassMapping<PersonSet>
{
public PersonSetMap()
{
Id(x => x.Id, m=>m.Generator(Generators.Identity));
Property(x=>x.Name);
Set(x => x.Cars, c =>
{
//c.Key(k =>
// {
// k.Column("PersonId");
// });
c.Cascade(Cascade.All);
c.Lazy(CollectionLazy.NoLazy);
// c.Inverse(true);
}
, r =>
{
r.OneToMany();
}
);
}
}
public class CarSetMap : ClassMapping<CarSet>
{
public CarSetMap()
{
Id(x => x.Id, m => m.Generator(Generators.Identity));
Property(x => x.Name);
ManyToOne(x => x.Person, m =>
{
m.Column("PersonId");
m.Cascade(Cascade.None);
m.NotNullable(true);
});
}
}
The problem I have is that if I update one car and try to save it on a person object, it doesn't change.
Update
I want to find out if it is possible, and where my mapping above is wrong. Any idea on either xml version or Loquacious would also be appreciated.
There should be a PersonId foreign key on table Car.
I don't know if this would solve your problem, but in a ManyToOne mapping the Unique and NotNullable methods should be applied at the column level.
ManyToOne(x => x.Person, m =>
{
m.Column(c =>
{
c.Name("PersonId");
c.NotNullable(true);
});
m.Cascade(Cascade.None);
});

Entity Framework 4.1: Using TPH and mapping properties to existing database columns

I got this simple class hierarchy;
public class A
{
[Key]
public int Id { get; set; }
}
public class B : A
{
public string Name { get; set; }
}
public class C : A
{
public string Name { get; set; }
}
Using TPH this will end upp with a table looking something like this;
Table A, fields Id, Name, Name1, Discriminator.
I want class B and C name property to map to the same field, ie Name, using these mappings.
Property(x => x.Id)
.HasColumnName("Id");
Map<B>(m =>
{
m.Properties(p => new
{
Name = p.Name,
});
});
Map<C>(m =>
{
m.Properties(p => new
{
Name = p.Name,
});
});
How can I make sure to map to the same column for my subtypes (B and C) property Name? Is there a HasColumnName to be used with the Properties collection?
THP does not allow for you to share properties between the objects unless the property is on the base class. Using TPH, a property is either Shared between all implementations or belongs to one of the specialized implementations.
Place Name property on the base class and this should solve your problem.

How to automap this(mapping sub members)

I have something like this
public class ProductViewModel
{
public int SelectedProductId { get; set; }
public string ProductName {get; set;}
public int Qty {get; set;}
public List<SelectListItem> Products { get; set};
}
I have a domain like this
public class Product
{
public int ProductId {get; set;}
public string ProductName {get; set;}
public int Qty {get; set;}
}
public class Store
{
public Product() {get; set;}
}
Now I need to do the mapping.
// in my controller
var result = Mapper.Map<ProductViewModel, Store>(Product);
this won't bind anything since it can't figure out how to put the ProductId in since it
is
Store.Product.ProductId;
My map is like this
Mapper.CreateMap<ProductViewModel, Store>().ForMember(dest => dest.Product.ProductId, opt => opt.MapFrom(src => src.SelectedProductId));
I get this error
Expression 'dest =>
Convert(dest.Product.SelectedProductId' must
resolve to top-level member. Parameter
name: lambdaExpression
I am unsure how to do this.
To Map nested structures, you just need to create a new object in the MapFrom argument.
Example
Mapping:
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.MyNestedType, o => o.MapFrom(t => new NestedType { Id = t.Id }));
Mapper.AssertConfigurationIsValid();
Test Code:
var source = new Source { Id = 5 };
var destination = Mapper.Map<Source, Destination>(source);
Classes:
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public NestedType MyNestedType { get; set; }
}
public class NestedType
{
public int Id { get; set; }
}
You can use Resolver.
Create a resolver class like that :
class StoreResolver : ValueResolver<Store, int>
{
protected override int ResolveCore(Store store)
{
return store.Product.ProductId;
}
}
And use it like that :
Mapper.CreateMap<ProductViewModel, Store>()
.ForMember(dest => dest.SelectedProductId, opt => opt.ResolveUsing<StoreResolver >());
Hope it will help ...
The error your getting is because you cannot declare mapping declarations more than one level deep in your object graph.
Because you've only posted one property its hard for me to give you the codes that will make this work. One option is to change your viewmodel property to MyTestTestId and the conventions will automatically pick up on that.
The correct answer given by allrameest on this question should help: AutoMapper - Deep level mapping
This is what you need:
Mapper.CreateMap<ProductViewModel, Store>()
.ForMember(dest => dest.Product, opt => opt.MapFrom(src => src));
Mapper.CreateMap<ProductviewModel, Product>()
.ForMember(dest => dest.ProductId, opt => opt.MapFrom(src => src.SelectedProductId));
NOTE: You should try to move away from using Mapper.CreateMap at this point, it is obsolete and will be unsupported soon.

Categories