Find out mapped property using AutoMapper - c#

I'm looking to 'map' errors when persisting DTOs back to the property in a viewmodel corresponding to the DTO property that caused the error, PersonDto.PreferedName causes a DB error, say Cannot be null, and this property maps to the viemodel PersonViewModel.Name, I want to be able to display an error message in the langiage of the view, e.g. "Name is required".
Is there any way I can query Mapper or some other AutoMapper object to find which viewmodel property PreferedName maps to?

It looks like you are getting the errors from the ModelState but to answer the original question you can use the following
using AutoMapper;
using System;
using System.Linq;
namespace ConsoleApplicationAutoMapper
{
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.DtoMyProperty, x => x.MapFrom(y => y.MyProperty)
));
Order order = new Order() { MyProperty = 12 };
var typeMaps = config.CreateMapper()
.ConfigurationProvider
.GetAllTypeMaps()
.Where(t => t.SourceType == typeof(Order) && t.DestinationType == typeof(OrderDto))
.Single()
.GetPropertyMaps();
foreach (var map in typeMaps)
Console.WriteLine(map.SourceMember.Name + "->" + map.DestinationProperty.Name);
Console.ReadLine();
}
}
public class Order
{
public int MyProperty { get; set; }
}
public class OrderDto
{
public int DtoMyProperty { get; set; }
}
}

As per comments, you can annotate your viewmodel with [Required] (System.ComponentModel.DataAnnotations.RequiredAttribute) and check in your controller action for ModelState.IsValid.
Serializing the contents of ModelState.Values and returning to your WPF front end will allow you to display any validation errors in whatever way suits.

Related

AutoMapper does not ignore List when mapping from source to existing destination

I'm having a small problem with AutoMapper. I have isolated the issue I am facing, if it actually is an issue and not just a misunderstanding.
Here are the classes I am working with:
public class DemoEntity
{
public List<string> Items { get; set; }
public string Name { get; set; }
}
public class DemoDto
{
public List<string> Items { get; set; }
public string Name { get; set; }
}
public class DemoProfile : Profile
{
public DemoProfile()
{
CreateMap<DemoDto, DemoEntity>()
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
}
}
In the Dependency Injection part (which seems to be in Program.cs for .NET 6, but is in Startup.cs in my main project), I have this code which I have read that should help allow nullable collections:
builder.Services.AddAutoMapper(configAction => { configAction.AllowNullCollections = true; }, typeof(Program));
Here is my test-code:
var dto = new DemoDto();
var entity = new DemoEntity()
{
Items = new List<string>() { "Some existing item" },
Name = "Existing name"
};
// Works as expected
var newEntity = _mapper.Map<DemoEntity>(dto);
// Sets the entity.Items to an empty list
_mapper.Map(dto, entity);
As you can see in the DemoProfile constructor, I set a condition to only map if srcMember != null, which works for the Name property. With the AllowNullCollections in the service registration, I can map to a new object with a null list (would be an empty list without the AllowNullCollections part).
My expected outcome would be AutoMapper to see that dto.Items is null, and not touch the entity.Items property during the mapping, and leave it with 1 string in the list. The actual outcome is that entity.Items is a list with 0 items. Name property is ignored.
Am I missing something? How can I adjust my code to work so that AutoMapper ignores a list that is null during mapping on an existing destination?
You can look for PreCondition to prevent the mapping from source when the source's member (with the array, List) is null or empty.
CreateMap<DemoDto, DemoEntity>()
.ForMember(dest => dest.Items, opt => opt.PreCondition((source, dest) =>
{
return source.Items != null && source.Items.Count > 0;
}))
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
Sample demo on .NET Fiddle

AutoMapper - Map child property hiding base class property

I'm encountering a quite stupid issue while trying to map a class to a derived class using AutoMapper with C#.
These are my classes:
class BaseParent {
public string Name { get; set; }
public BaseChild Child { get; set; }
}
class BaseChild {
public int Age { get; set; }
}
class DerivedParent : BaseParent {
public new DerivedChild Child { get; set; }
}
class DerivedChild : BaseChild { }
In particular, what I'm trying to achieve is that all the properties of the mapped class are correctly set. The issue is that the Child property of the mapped class is not set and remains null.
This is the mapping configuration I'm using:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<BaseChild, DerivedChild>();
cfg.CreateMap<BaseParent, DerivedParent>()
.ForMember(dest => dest.Child, opt => opt.MapFrom(src => src.Child));
});
Any help is appreciated.
Thanks
EDIT
Actually is not correct to say that Child property remains null.
Debugging the code I can see that there are 2 child properties with the same name because of the new modifier used to hide the parent one.
Anyway, the property I need is still null.
Are you sure you're looking at it correctly?
Given the following config:
// Arrange
var mapper = new Mapper(new MapperConfiguration(cfg => {
cfg.CreateMap<BaseChild, DerivedChild>();
cfg.CreateMap<BaseParent, DerivedParent>()
.ForMember(dest => dest.Child, opt => opt.MapFrom(src => src.Child));
}));
var baseParent = new BaseParent { Name = "A", Child = new BaseChild { Age = 1 } };
// Act
var derived = mapper.Map<DerivedParent>(baseParent);
I can assure you that:
derived.Child is certainly not null
It is of type DerivedChild
The hidden BaseChild is null
Which you can see in the following (see working Fiddle):
// Assert
Assert.IsNotNull(derived);
Assert.IsInstanceOf<DerivedParent>(derived);
// The 'new' Child property is not null
Assert.IsNotNull(derived.Child);
Assert.IsInstanceOf<DerivedChild>(derived.Child);
// The hidden property should be null
Assert.IsNull(((BaseParent)derived).Child);

Entity Framework Core 2.1 - owned types and nested value objects

I'm learning DDD and the tutorial I'm currently following is implemented using NHibernate, but since my lack of experience with it I've decided to go through the course using EF Core 2.1.
However, I'm currently a bit stuck with the following: I have three classes Customer which is an entity and two value objects (CustomerStatus and inside of it value object ExpirationDate) - like this:
public class Customer : Entity
{
//... constructor, other properties and behavior omitted for the sake of simplicity
public CustomerStatus Status { get; set; }
}
public class CustomerStatus : ValueObject<CustomerStatus>
{
// customer status is enum containing two values (Regular,Advanced)
public CustomerStatusType Type { get; }
public ExpirationDate ExpirationDate { get; }
}
public class ExpirationDate : ValueObject<ExpirationDate>
{
//... constructor, other properties and behavior omitted for the sake of simplicity
public DateTime? Date { get; private set; }
}
When I try to do the following inside of my DbContext:
modelBuilder.Entity<Customer>(table =>
{
table.OwnsOne(x => x.Status,
name =>
{
name.Property(x => x.ExpirationDate.Date).HasColumnName("StatusExpirationDate");
name.Property(x => x.Type).HasColumnName("Status");
});
});
I'm getting the following error:
The expression 'x => x.ExpirationDate.Date' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'.
Parameter name: propertyAccessExpression'
Beside this I've tried doing things like:
table.OwnsOne(x => x.Status.ExpirationDate,
name =>
{
name.Property(x => x.Date).HasColumnName("StatusExpirationDate");
});
table.OwnsOne(x => x.Status,
name =>
{
name.Property(x => x.Type).HasColumnName("Status");
});
But it also leads to:
The expression 'x => x.Status.ExpirationDate' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'.
I've also tried:
modelBuilder.Entity<Customer>()
.OwnsOne(p => p.Status,
cb => cb.OwnsOne(c => c.ExpirationDate));
But with no luck as well... Anyways, any help would be greatly appreciated, also if possible it would be really great if someone could explain why none of my tries are not working? Thanks in advance!
UPDATE
After doing as stated in Ivan's comment first I was getting error about CustomerStatus class constructor so I've added default protected one.
After that I started getting error:
Field 'k__BackingField' of entity type 'CustomerStatus' is readonly and so cannot be set.
Here's inner of my CustomerStatus class if it helps:
public class CustomerStatus : ValueObject<CustomerStatus>
{
public CustomerStatusType Type { get; }
public ExpirationDate ExpirationDate { get; }
public static readonly CustomerStatus Regular =
new CustomerStatus(CustomerStatusType.Regular, ExpirationDate.Infinite);
public bool IsAdvanced => Type == CustomerStatusType.Advanced && !ExpirationDate.IsExpired;
private CustomerStatus(CustomerStatusType type, ExpirationDate expirationDate)
{
Type = type;
ExpirationDate = expirationDate;
}
protected CustomerStatus()
{
}
public static CustomerStatus Create(CustomerStatusType type, ExpirationDate expirationDate)
{
return new CustomerStatus(type, expirationDate);
}
public CustomerStatus Promote()
{
return new CustomerStatus(CustomerStatusType.Advanced, ExpirationDate.Create(DateTime.UtcNow.AddYears(1)).Value);
}
protected override bool EqualsCore(CustomerStatus other)
{
return Type == other.Type && ExpirationDate == other.ExpirationDate;
}
protected override int GetHashCodeCore()
{
return Type.GetHashCode() ^ ExpirationDate.GetHashCode();
}
}
UPDATE
All it took was adding private setters on Type and ExpirationDate properties inside of CustomerStatus class and in combination with Ivan's answer it works like a charm. Thanks a lot!
Your attempts are not working because owned types can only be configured through their owner entity, and more specifically, through their own builder returned by the OwnsOne method or provided as an argument of the Action<T> argument of the OwnsOne method of the owner entity builder.
So the configuration should be something like this (note the nested OwnsOne):
modelBuilder.Entity<Customer>(customer =>
{
customer.OwnsOne(e => e.Status, status =>
{
status.Property(e => e.Type).HasColumnName("Status");
status.OwnsOne(e => e.ExpirationDate, expirationDate =>
{
expirationDate.Property(e => e.Date).HasColumnName("StatusExpirationDate");
});
});
});

Same property name in several child entities with Entity Framework Core 2.0

I'm currently using the simple model below. It's pretty straightforward: we have resources and they can be Room, EmptyOffice (...) or Service.
Room and EmptyOffice can have a capacity, but NOT Service.
public abstract class Resource : Entity
{
public string Name { get; set; }
}
public class Room : Resource
{
public int Capacity { get; set; }
}
public class EmptyOffice : Resource
{
public int Capacity { get; set; }
}
public class Service : Resource
{ }
To get the data from my SQL view, I use the mappings:
builder.Entity<Resource>(m =>
{
m.ToTable("resource", "facility");
m.HasKey(x => x.Id);
m.Property(x => x.Id)
.HasColumnName("ResourceId");
m.Property(x => x.Type)
.HasColumnName("ResourceTypeId");
m.HasDiscriminator(x => x.Type)
.HasValue<Room>(ResourceType.Room)
.HasValue<EmptyOffice>(ResourceType.EmptyOffice)
.HasValue<Service>(ResourceType.Service);
});
builder.Entity<Room>();
builder.Entity<EmptyOffice>();
builder.Entity<Service>();
When I run my code, EF Core throws the following exception:
System.Data.SqlClient.SqlException: 'Invalid column name 'Room_Capacity'.'
If I rename the Capacity property to Room_Capacity, it works but it's horrible.
How can I force EF Core 2.0 to target the capacity property for each of my child entities?
Thank you
Sebastien
This worked for me:
builder.Entity<Room>().Property(a => a.Capacity).HasColumnName("Capacity");
builder.Entity<EmptyRoom>().Property(a => a.Capacity).HasColumnName("Capacity");
You can't do that as the only inheritance pattern available in EF Core is table per class hierarchy. If you go with interfaces instead of base classes, you can, but each entity will be mapped to a different table. Mark any property you want to exclude with [NotMapped], or, using the code, with Ignore.
I did in project next code to make it more generic way.
private static void FindAndConfigureBackgroundJobResultTypes(ModelBuilder modelBuilder)
{
var backgroundJobResultTypes = typeof(BackgroundJobResult).Assembly.GetTypes().Where(x => x.IsSubclassOf(typeof(BackgroundJobResult))).ToList();
var sameTypeAndNameProperties = backgroundJobResultTypes
.SelectMany(x => x.GetProperties())
.GroupBy(d => new {d.Name, d.PropertyType})
.Select(grp => new
{
PropertyType = grp.Key.PropertyType,
PropertyName = grp.Key.Name,
Count = grp.Count()
})
.Where(x => x.Count > 1).ToList();
foreach (var backgroundJobResultType in backgroundJobResultTypes)
{
//Set base type , instead of exposing this type by DbSet
modelBuilder.Entity(backgroundJobResultType).HasBaseType(typeof(BackgroundJobResult));
//Map properties with the same name and type into one column, EF Core by default will create separate column for each type, and make it really strange way.
foreach (var propertyInfo in backgroundJobResultType.GetProperties())
{
if (sameTypeAndNameProperties.Any(x => x.PropertyType == propertyInfo.PropertyType && x.PropertyName == propertyInfo.Name))
{
modelBuilder.Entity(backgroundJobResultType).Property(propertyInfo.PropertyType, propertyInfo.Name).HasColumnName(propertyInfo.Name);
}
}
}
}

How to use AutoMapper?

First time using AutoMapper and I'm have a hard time figuring out how to use it.
I'm trying to map a ViewModel to my Database Tables.
My ViewModel looks like this...
public class AddressEditViewModel
{
public AddressEdit GetOneAddressByDistrictGuid { get; private set; }
public IEnumerable<ZipCodeFind> GetZipCodes { get; private set; }
public AddressEditViewModel(AddressEdit editAddress, IEnumerable<ZipCodeFind> Zips)
{
this.GetOneAddressByDistrictGuid = editAddress;
this.GetZipCodes = Zips;
}
}
The Mapping I'm trying to use is...
CreateMap<Address, AddressEditViewModel>();
When I run this test...
public void Should_map_dtos()
{
AutoMapperConfiguration.Configure();
Mapper.AssertConfigurationIsValid();
}
I get this error...
AutoMapper.AutoMapperConfigurationException: The following 2 properties on JCIMS_MVC2.DomainModel.ViewModels.AddressEditViewModel
are not mapped:
GetOneAddressByDistrictGuid
GetZipCodes
Add a custom mapping expression, ignore, or rename the property on JCIMS_MVC2.DomainModel.Address.
I'm not sure how I am supposed to map those 2 properties. I would appreciate any direction. Thanks
Mark
Ok so I can see a few things you are doing that probably won't help.
Firstly this AutoMapper is used to copy Properties in one object to Properties in a diff object. Along the way it might interrogate or manipulate them to get the end result viewmodel in the correct state.
The properties are named 'Get...' which sounds more like a method to me.
The setters on your properties are private so AutoSetter won't be able to find them. Change these to minimum internal.
Use of a parametrized constructor is no longer needed when you use AutoMapper - as you are converting directly from one object to another. The parametised constructor is there mainly to show what is explicitly required by this object.
CreateMap<Address, AddressEditViewModel>()
.ForMember( x => x.GetOneAddressByDistrictGuid ,
o => o.MapFrom( m => m."GetOneAddressByDistrictGuid") )
.ForMember( x => x.GetZipCodes,
o => o.MapFrom( m => m."GetZipCodes" ) );
What Automapper is really good for is copying from DataObjects into POCO objects, or View Model objects.
public class AddressViewModel
{
public string FullAddress{get;set;}
}
public class Address
{
public string Street{get;set;}
public string Suburb{get;set;}
public string City{get;set;}
}
CreateMap<Address, AddressViewModel>()
.ForMember( x => x.FullAddress,
o => o.MapFrom( m => String.Format("{0},{1},{2}"), m.Street, m.Suburb, m.City ) );
Address address = new Address(){
Street = "My Street";
Suburb= "My Suburb";
City= "My City";
};
AddressViewModel addressViewModel = Mapper.Map(address, Address, AddressViewModel);

Categories