Automapper ConstructUsing not working as expected - asp.net-core

I am using automapper in my asp.net core project and it's my first time with that library. The data flow is as follows: Model->DomainModel->ViewModel. Automapper is used for mapping between those. I have problems using ConstructUsing. It seems to me it is not working.
Part of the data profile class:
CreateMap<ClinicD, ClinicViewModel>()
.ConstructUsing(x => new ClinicViewModel
{
Active = x.Active,
CooperationStart = x.CooperationStart,
CooperationEnd = x.CooperationEnd,
Id = x.Id,
Name = x.Name,
AddressId = x.Address.Id,
FlatNo = x.Address.FlatNo,
City = x.Address.City,
HouseNumber = x.Address.HouseNumber,
Street = x.Address.Street,
Postcode = x.Address.Postcode
})
.ForMember(x => x.ChosenSpecialities, opt => opt.Ignore())
.ForMember(x => x.Specialities, opt => opt.Ignore());
public class ClinicViewModel : CooperationSpotViewModel
{
private IEnumerable<int> _chosenSpecialities;
public IEnumerable<SelectListItem> Specialities
{
get;set;
}
public IEnumerable<int> ChosenSpecialities
{
get
{
if (_chosenSpecialities == null)
_chosenSpecialities = new List<int>();
return _chosenSpecialities;
}
set
{
if (value != null)
_chosenSpecialities = value;
}
}
}
public abstract class CooperationSpotViewModel : BaseViewModel
{
public int? Id { get; set; }
public string Name { get; set; }
public DateTime? CooperationStart { get; set; }
public DateTime? CooperationEnd { get; set; }
public bool? Active { get; set; }
public string PhoneNumber { get; set; }
public int? AddressId { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
public string FlatNo { get; set; }
public string City { get; set; }
public string Postcode { get; set; }
}
public class ClinicD : CooperationSpotD
{
public IEnumerable<SpecialityD> Specialities
{
get;set;
}
}
public abstract class CooperationSpotD
{
public int? Id { get; set; }
public string Name { get; set; }
public DateTime? CooperationStart { get; set; }
public DateTime? CooperationEnd { get; set; }
public bool? Active { get; set; }
public string PhoneNumber { get; set; }
public AddressD Address { get; set; }
}
A similar issue occurs for me in several spots, so I am guessing, I must be doing something basic wrong. The exception that occurs:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
ClinicD -> ClinicViewModel (Destination member list)
SeeingEyeDog.BusinessLogic.Models.ClinicD -> SeeingEyeDog.Models.ClinicViewModel (Destination member list)
Unmapped properties:
Street
HouseNumber
FlatNo
City
Postcode

Related

How to get the discriminator from the id of an entitie?

I would like to now wether my id is an invoice or an individualinvoice
individualinvoice.cs
public class IndividualInvoice : Invoice {
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
invoice.cs
public class Invoice {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
[Required]
public string Company { get; set; }
[Required]
public string Address { get; set; }
[Required]
public int HouseNumber { get; set; }
[Required]
public string Zipcode { get; set; }
[Required]
public string City { get; set; }
[Required]
public string Country { get; set; }
[Required]
public string VATNumber { get; set; }
public Customer Customer { get; set; }
[ForeignKey("Customer")]
[Required]
public string CustomerId { get; set; }
}
gingsengdbcontext.cs
public class GingsengDbContext : IdentityDbContext<GingsengUser> {
public DbSet<Gingseng> Gingsengs { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<IndividualInvoice> IndividualInvoices { get; set; }
public GingsengDbContext(DbContextOptions<GingsengDbContext> options) : base(options)
{
}
}
And here is my controller where i would like to know from the id if the id corresponds to an individialinvoice or just an invoice? is there any cleaner way than to use singleordefault?
public class InvoicesController : Controller {
private readonly GingsengDbContext context;
private readonly IMapper mapper;
public InvoicesController(GingsengDbContext context, IMapper mapper)
{
this.context = context;
this.mapper = mapper;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetInvoice(string id) {
}
}
Well, the only clean way which works with all EF Core supported inheritance models (currently TPH and TPT) is to use C# is operator. However the classes must not inherit other non abstract class from the same hierarchy like in your example, because IndividualInvoice is a Invoice, hence will be included in DbSet<Invoice> and any query (OfType etc.) which checking for Invoice.
So you can check just for final classes, e.g.
bool isIndividualInvoice = await context.Invoices
.AnyAsync(e => e.Id == id && e is IndividualInvoice);
which btw is the same as
bool isIndividualInvoice = await context.IndividualInvoices
.AnyAsync(e => e.Id == id);
and similar (using Set<IndividualInvoice>() or Set<Invoice>().OfType<IndividualInvoice>).
Another not so clean option which works only for TPH is to retrieve the discriminator property value directly. You have to know its name and type (the defaults are "Discriminator" and string) and use the special EF.Property method similar to this:
var type = await context.Invoices
.Where(e => e.Id == id)
.Select(e => EF.Property<string>(e, "Discriminator")) // <--
.FirstOrDefaultAsync();
// here type will be ether null, "Invoice" or "IndividualInvoice"

Problem with mapping two objects (with lists)

I am looking for solution my issue... Probably my Shifts class cannot be mapped.
I have entity class Worker:
public class Worker
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[MaxLength(200)]
public string PhotoFilePath { get; set; }
public Workplace Workplace { get; set; }
public int WorkplaceId { get; set; }
public List<Service> Services { get; set; }
public List<Shift> Shifts { get; set; }
public IEnumerable<Worker> ToList()
{
throw new NotImplementedException();
}
}
And model WorkerModel:
public int Id { get; set; }
[Required]
[DisplayName("Imię")]
public string Name { get; set; }
[DisplayName("Nazwisko")]
public string LastName { get; set; }
[Display(Name = "Zdjęcie")]
public IFormFile Photo { get; set; }
public string PhotoFilePath { get; set; }
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int WorkplaceId { get; set; }
public List<ServiceModel> Services { get; set; }
public List<ShiftModel> Shifts { get; set; }
}
My default mapper profile:
//Mapping workers
CreateMap<Worker, WorkerModel>();
CreateMap<WorkerModel, Worker>();
And when I try map model to entity class in my action:
Worker worker = _mapper.Map<Worker>(model);
I get an issue:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
This is caused by different mapping types. Take the property Service as an example.
The resource is a type of Service.
But the destination is a type of ServiceModel.
So, they need to be converted. Here is a demo.
I create the Service and ServiceModel according to your model.
public class Service
{
public int serviceID { get; set; }
public string myservice { get; set; }
}
public class ServiceModel
{
public int serviceID { get; set; }
public string myservice { get; set; }
}
This is mapping relationship.
public class AutomapProfile : Profile
{
public AutomapProfile()
{
CreateMap<Worker, WorkerModel>();
CreateMap<WorkerModel, Worker>()
.ForMember(m => m.Services, x => x.MapFrom(y => y.Services.Select(a=>
new Service
{
serviceID=a.serviceID,
myservice=a.myservice
})));
}
}
This is the mapping method.
public IActionResult Index()
{
var model = new WorkerModel
{
Id=1,
Name="names",
//...
Services = new List<ServiceModel>
{
new ServiceModel{ serviceID=1, myservice="service1"},
new ServiceModel{ serviceID=2, myservice="service2"},
},
//...
};
Worker worker = _mapper.Map<Worker>(model);
return Ok(worker);
}
Result.

map string filed to generic list in automapper based on .net core

I have a DomainModel and a DTO like this :
public class PostEntity: IEntity
{
[Required]
public string Description { get; set; }
public int Id { get; set; }
[Required]
public string Slug { get; set; }
[Required]
public string Tags { get; set; }
[Required]
public string Title { get; set; }
[Required]
public DateTime CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public PostStatus Status { get; set; }
public User Writer { get; set; }
public int WriterId { get; set; }
}
public class PostDto
{
public int Id { get; set; }
public string Description { get; set; }
public string Slug { get; set; }
public string Tags { get; set; }
public string Title { get; set; }
public DateTime CreatedOn { get; }
public List<string> TagList { get; set; }
public PostDto()
{
TagList = new List<string>();
}
}
PostEntity'Tags contains some tags seperated by ",", now I want to split tags value by "," and convert it to List, to do this, I've tried this but I get the below compilation error
CreateMap<PostEntity, PostDto>().ForMember(dest => dest.TagList, cc => cc.MapFrom(src => src.Tags.Split(",").ToList()));
I get this error :
An expression tree may not contain a call or invocation that uses optional arguments
I can't reproduce your error, it seems to work fine.
Below is an example where the TagList is correctly mapped
The code I used :
MapperConfiguration MapperConfiguration = new MapperConfiguration(configuration =>
{
configuration
.CreateMap<PostEntity, PostDto>().ForMember(dest => dest.TagList, cc => cc.MapFrom(src => src.Tags.Split(',').ToList()));
});
IMapper mapper = MapperConfiguration.CreateMapper();
PostEntity postEntity = new PostEntity
{
Tags = "Tag1,Tag2,Tag3,Tag4"
};
var mappedObject = mapper.Map<PostEntity, PostDto>(postEntity);
Please bear in mind that Expression.Call API does not support optional parameters. So, you should Replace Split(',') with
Split(',', System.StringSplitOptions.None)
or
Split(',', System.StringSplitOptions.RemoveEmptyEntries)
doing so you won't see that error again.

Relations with DbQuery

I have this Participant model DbSet<Participants>:
public class Participant {
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int IsCaptain { get; set; }
public Guid TeamId { get; set; }
[ForeignKey("TeamId")]
public Team Team { get; set; }
}
And this ParticipantDataView DbQuery<ParticipantsDataView>:
public class ParticipantDataView {
public Guid Id { get; set; } // = Participant.Id
public double? FirstWeight { get; set; }
public double? LastWeight { get; set; }
public double? WeightLoss => FirstWeight - LastWeight;
public Participant Participant { get; set; }
}
DbContext:
public class DBContext : DbContext {
public DBContext(DbContextOptions<DBContext> options) : base(options) {}
public DbSet<Participant> Participants { get; set; }
public DbQuery<ParticipantDataView> ParticipantsDataView { get; set; }
}
My query:
Participants = await _context.ParticipantsDataView
.Include(p => p.Participant)
.ThenInclude(t => t.Team)
.Where(p => p.Participant.Status == 1 && p.Participant.Team.Status == 1).OrderBy(p => p.WeightLoss)
.AsNoTracking()
.ToListAsync();
The error:
SqlNullValueException: Data is Null.
This method or property cannot be called on Null values.
Every item in ParticipantDataView has a match in Participants so I don't know why I get this error?

Column Property AutoMapping

There is o possibility to create a convention for Column naming:
I have this piece of code:
public AutoPersistenceModel Generate()
{
var result = AutoPersistenceModel.MapEntitiesFromAssemblyOf<User>()
.Where(GetAutoMappingFilter)
.WithConvention(GetConventions);
return result;
}
private bool GetAutoMappingFilter(Type t)
{
return
t.GetInterfaces().Any(
x => x.IsGenericType && (x.GetGenericTypeDefinition() == typeof(IEntityWithTypedId<>)));
}
private static void GetConventions(Conventions conventions)
{
conventions.GetPrimaryKeyNameFromType = type => type.Name.ToLower() + "_id";
conventions.FindIdentity = type => type.Name.ToLower() == "id";
conventions.GetTableName = type =>
{
if (!type.Name.Contains("Lookup"))
{
return Inflector.Net.Inflector.Pluralize(type.Name).ToLower();
}
return Inflector.Net.Inflector.Underscore(type.Name)
.Replace("lookup", "lu").ToLower();
};
conventions.IsBaseType = DontMapAsJoinedSubclassTypesInheritedFrom;
conventions.GetForeignKeyNameOfParent = type => Inflector.Net.Inflector.Underscore(type.Name) + "_id";
conventions.GetForeignKeyName = type => Inflector.Net.Inflector.Underscore(type.Name) + "_id";
conventions.OneToManyConvention = m => m.Cascade.All();
}
private static bool DontMapAsJoinedSubclassTypesInheritedFrom(Type arg)
{
var derivesFromEntity = arg == typeof(Entity);
var derivesFromEntityWithTypedId = arg.IsGenericType &&
(arg.GetGenericTypeDefinition() == typeof(EntityWithTypedId<>));
return derivesFromEntity || derivesFromEntityWithTypedId;
}
and an Entity class
public class Organisation : EntityWithTypedId<int>
{
public virtual Organisation Parent { get; set; }
public virtual LookOrganisationType OrganisationType { get; set; }
[DomainSignature]
public virtual string OrganisationName { get; set; }
public virtual string Address1 { get; set; }
public virtual string Address2 { get; set; }
public virtual string City { get; set; }
public virtual string Postcode { get; set; }
public virtual LookupCountry Country { get; set; }
public virtual string TelNo { get; set; }
public virtual string FaxNo { get; set; }
public virtual string Email { get; set; }
public virtual User Contact { get; set; }
public virtual DateTime? DateCreated { get; set; }
public virtual DateTime? DateAmended { get; set; }
public virtual bool Active { get; set; }
}
Finally I want to have a column for, let's say TelNo like tel_no.
So the Convention is if Property contains capital letter in the middle ti should be underscored. Inflector.Net.Inflector.Underscore works fine. But I do not know how to write the convention.
Somethig like:
(conventions.GetPropertyName = type => Inflector.Net.Inflector.Underscore(type.ColumnName))
Thanks