I would like to assign a value manually to a DTO property in the Profile of the AutoMapper depending on the value I have in my entity.
Below you can see my code, but it doesn't work as expected because the .AfterMap is independent from the .Condition, as a matter of fact every dest.Sent is mapped with true:
.ForMember(
dest => dest.Sent,
opts => opts.Condition(src => src.Status == 2 && src.Status != 1)
)
.AfterMap((notification, dto) =>
dto.Sent = true);
My DTO
public class NotificationItemDTO
{
public long Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public bool Sent { get; set; }
}
My Source
public class Notification
{
public long Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public int Status { get; set; }
}
I wish to assign a value to dest.Sent which is a bool depending on src.Status which is an int. So if src.Status == 1, my dest.Sent = false, if src.Status == 2, my dest.Sent = true. Is it possible to achieve this?
You have misunderstood the usage of .Condition().
AutoMapper allows you to add conditions to properties that must be met before that property will be mapped.
Hence, from your scenario, you are trying to assign a different value to the destination based on condition but not block the mapping from source to destination by condition which is the main purpose of .Condition().
While .AfterMap() is the last action to be executed after the mapping definition logic is executed, hence the value will be overridden.
Using .MapFrom() and defining the logic to set the destination's Sent is true only when the source's Status is 2.
CreateMap<Notification, NotificationItemDTO>()
.ForMember(
dest => dest.Sent,
opts => opts.MapFrom(src => src.Status == 2)
);
Demo # .NET Fiddle
Related
I want to make PUT request in ASP .NET CORE that user can make to update his note.
It's my UpdateNoteDTO:
public class UpdateNoteDto
{
[MaxLength(50)]
public string NoteTitle { get; set; }
[MaxLength(500)]
public string NoteBody { get; set; }
public int Color { get; set; }
}
and it's my Update method:
public void Update(UpdateNoteDto dto, int noteID)
{
var note = _dbContext
.Notes
.FirstOrDefault(r => r.Id == noteID);
if (note is null)
{
throw new NotFoundException("Note not found");
}
note.NoteTitle = dto.NoteTitle == string.Empty
|| dto.NoteTitle is null
? note.NoteTitle : dto.NoteTitle;
note.NoteBody = dto.NoteBody == string.Empty
|| dto.NoteBody is null
? note.NoteBody : dto.NoteBody;
note.Color = dto.Color == 1 ? note.Color : dto.Color;
_dbContext.SaveChanges();
}
I want to make request that user can make to change single field without need for declare all of them. This code what i wrote is working but i bet there is better solution for this :D
The way you are doing it looks fine but I would suggest two changes,
If you plan to use the endpoint for updating one or some values it's better to change request type to PATCH, the HTTP PATCH request method applies partial modifications to a resource whereas HTTP PUT changes whole resource.
To easily add conditions for mapping DTOs to entity models I would rather use Automapper which allows user to declare mapping conditions in a single spot and use IMapper interface to map the models.
For example if you have two classes:
public class UpdateNoteDto
{
[MaxLength(50)]
public string NoteTitle { get; set; }
[MaxLength(500)]
public string NoteBody { get; set; }
public int Color { get; set; }
}
public class Note
{
public int Id { get; set; }
public string NoteTitle { get; set; }
public string NoteBody { get; set; }
public int Color { get; set; }
}
you can map them by creating a mapping profile with conditions instead of using if, switch or any other way of comparing whether to map or not
mapping Profile could look like this:
public class NoteProfile: Profile
{
public NoteProfile()
{
CreateMap<UpdateNoteDto, Note>()
.ForMember(dest => dest.NoteTitle, opt => opt.Condition(src => !string.IsNullOrEmpty(src.NoteTitle)))
.ForMember(dest => dest.NoteBody, opt => opt.Condition(src => !string.IsNullOrEmpty(src.NoteBody)))
.ForMember(dest => dest.Color, opt => opt.Condition(src => src.Color != default))
.ForMember(dest => dest.Id, opt => opt.Ignore());
}
}
and update the Update(UpdateNoteDto dto, int noteID) function correspondingly:
public void Update(UpdateNoteDto dto, int noteID)
{
var noteToUpdate = _dbContext
.Notes
.FirstOrDefault(r => r.Id == noteID);
if (note is null)
{
throw new NotFoundException("Note not found");
}
var note = _mapper.Map(dto, note);
_dbContext.Update(note);
_dbContext.SaveChanges();
}
client should handled this issue
Or
if you want update an entity, it is better to use PATCH
If the client wants the previous data not to be edited
It should send you the previous data
Maybe, for example, the user wants to field the "NoteBody" value as empty In which case it cannot do this
I'm sure this is dead simple, but I need help ... I'm trying to display a single product in a view, and have this query:
var Product = await (from p in _context.Products
where p.Id == id
select p).FirstOrDefaultAsync();
Then I try to map the result to my viewmodel and return it to the view:
var VMProduct = _mapper.Map<ViewModelProduct, Product>(Product);
return View(VMProduct);
However, I get a build error on the mapping:
"Error CS1503 Argument 1: cannot convert from 'MyStore.Models.Product'
to MyStore.Models.ViewModels.ViewModelProduct'"
This is my entity model,
public class Product
{
public int Id { get; set; }
public string Title { get; set; }
public string Info { get; set; }
public decimal Price { get; set; }
public List<ProductInCategory> InCategories { get; set; }
}
and this is my viewmodel
public class ViewModelProduct
{
public int Id { get; set; }
public string Title { get; set; }
public string Info { get; set; }
public decimal Price { get; set; }
public int SortOrder { get; set; }
public IEnumerable<ViewModelCategoryWithTitle> Categories { get; set; }
public ViewModelProduct(ProductInCategory pic)
{
Id = pic.Product.Id;
Title = pic.Product.Title;
Price = pic.Product.Price;
Info = pic.Product.Info;
SortOrder = pic.SortOrder;
}
public ViewModelProduct() { }
}
This is my mapping profile:
CreateMap<Product, ViewModelProduct>();
CreateMap<ViewModelProduct, Product>();
Edit:
After changing
var VMProduct = _mapper.Map<ViewModelProduct, Product>(Product);
to
var VMProduct = _mapper.Map<Product, ViewModelProduct>(Product);
and adding Mapper.AssertConfigurationIsValid();, I get one step further, and am informed that SortOrder, Categories and InCategories are unmapped.
I'm reluctant to change my viewmodel (too much). Can I make the mapping work with the current viewmodel?
Edit 2:
Apparently, now it works. The unmapped properties are still unmapped, but when I removed Mapper.AssertConfigurationIsValid();, the view rendered just fine.
Note that you can define for each member how it should be mapped. This is necessary if the destination member has a different name than the source member.
If source and destination have different (complex) types, add an additional mapping config between these types.
If the member is not mapped, but set somewhere else (e.g. in the controller), ignore it to prevent an error when checking the configuration with Mapper.AssertConfigurationIsValid().
CreateMap<Product, ViewModelProduct>()
// other members will be mapped by convention, because they have the same name
.ForMember(vm => vm.SortOrder, o => o.Ignore()) // to be set in controller
.ForMember(vm => vm.Categories, o => o.MapFrom(src => src.InCategories));
// needed to resolve InCategories -> Categories
CreateMap<ViewModelCategoryWithTitle, ProductInCategory>();
Also, most of the time it is sufficient to tell Automapper just the destination type you want, and let it resolve which mapping to apply:
var VMProduct = _mapper.Map<ViewModelProduct>(Product);
public List<PropertyListDto> GetPropertiesByStatus(GetPropertyInput input)
{
//exception occurs here
var properties = _propertyRepository
.GetAll()
.Include(p => p.Address)
.ToList();
var results = new List<PropertyListDto>(properties.OrderBy(p => p.Id).MapTo<List<PropertyListDto>>());
return results;
}
[AutoMapFrom(typeof(Property))]
public class PropertyListDto : FullAuditedEntityDto
{
public new int Id { get; set; }
public CountyListDto County { get; set; }
public string Priority { get; set; }
public string Dist { get; set; }
public decimal ListingPrice { get; set; }
public string Blk { get; set; }
public AddressDto Address { get; set; }
public string Contact { get; set; }
public string Lot { get; set; }
public decimal Taxes { get; set; }
public string Mls { get; set; }
public ICollection<CommentEditDto> Comments { get; set; }
public int? LegacyId { get; set; }
}
Q : I need to show around 100,000 (1 lakh) data on the Angular UI Grid.But the problem is, above query gives memory exception.So could you tell me how to sort out this issue ? Thanks.
Note : I need to show the data without pagination.So I have selected this UI Grid.
UPDATE :
When I use .AsNoTracking(), then it works fine on the first query.
var properties = _propertyRepository
.GetAll()
.AsNoTracking()
.Include(p => p.Address)
.ToList();
But then the problem is on MapTo line.Could you tell me how to sort it out ? Thanks.
var results = new List<PropertyListDto>(properties.OrderBy(p => p.Id).MapTo<List<PropertyListDto>>());//error is here now
This is the error :
{"Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding."}
Mapping types:
Property_A048C3D093990BB6A086B710BAC90CB35FD4BAB180FC02FA3E90053FE58F20D3 -> ICollection`1
System.Data.Entity.DynamicProxies.Property_A048C3D093990BB6A086B710BAC90CB35FD4BAB180FC02FA3E90053FE58F20D3 -> System.Collections.Generic.ICollection`1[[IP.Comments.Dtos.CommentEditDto,IP.Application, Version=1.7.1.1, Culture=neutral, PublicKeyToken=null]]
Destination path:
List`1[3126].Comments3126[3126].Comments3126[3126]
Source value:
[Property_A048C3D093990BB6A086B710BAC90CB35FD4BAB180FC02FA3E90053FE58F20D3 3166]
UPDATE 2 : Here I have used Automapper to map EF object into Dto object.
I suppose you are initiate two ToList methods since I do not know what MapTo doing.
However instead doing mapping select directly your dto:
var properties = _propertyRepository
.GetAll()
.AsNoTracking()
.Include(p => p.Address).
.Select(s=> new PropertyListDto{
Id = s.Id
CountyListDto = s.CountyListDto
...
})
OP's Answer : Actually I have reduced all the unnecessary data on the above table and now it's having around 1K+ records.So no problem at all now.Cheers :)
I have a entity like
public class Program
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
}
and
public class EMetrics
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
public List<Program> Programs { get; set; }
}
I have repository method like,
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(x => x.ID)))
.ToList();
return metrics;
}
[The above code throwing build error]
Here only where I am facing problem to get the EMetrics based on the program Ids array params.
I want list Emetrics which are associated with the program.
You're incorrectly accessing the same input parameter in your LINQ. It should be refactored by changing your inner Select to use a different parameter:
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(y => y.ID)))
.ToList();
return metrics;
}
So you want to check if all elements of one collection are present in the other. In LINQ that can be done with combination of Except and Any:
var metrics = EntitySet
.Where(x => x.Programs.Select(p => p.ID).Except(programIds).Any())
.ToList();
Fyi - your current code is failing because Array.Contains expects a single item, an int in this case, while you are giving it a whole enumerable
Below are my classes
public class Student {
public long Id { get; set; }
public long? CollegeId { get; set; }
public StudentPersonal StudentPersonal { get; set; }
}
public class StudentPersonal {
public long? EthnicityId { get; set; }
public bool? GenderId { get; set; } // doesn't exist on UpdateModel, requires PropertyMap.SourceMember null check
}
public class UpdateModel{
public long Id { get; set; }
public long? CollegeId { get; set; }
public long? StudentPersonalEthnicityId { get; set; }
}
Below is the AutoMapper config
Mapper.Initialize(a => {
a.RecognizePrefixes("StudentPersonal");
}
Mapper.CreateMap<UpdateModel, StudentPersonal>()
.ForAllMembers(opt => opt.Condition(src => src.PropertyMap.SourceMember != null && src.SourceValue != null));
Mapper.CreateMap<UpdateModel, Student>()
.ForMember(dest => dest.StudentPersonal, opt => opt.MapFrom(src => Mapper.Map<StudentPersonal>(src)))
.ForAllMembers(opt => opt.Condition(src => src.PropertyMap.SourceMember != null && src.SourceValue != null));
And the sample test case:
var updateModel = new StudentSignupUpdateModel();
updateModel.Id = 123;
updateModel.CollegeId = 456;
updateModel.StudentPersonalEthnicityId = 5;
var existingStudent = new Student();
existingStudent.Id = 123;
existingStudent.CollegeId = 777; // this gets updated
existingStudent.StudentPersonal = new StudentPersonal();
existingStudent.StudentPersonal.EthnicityId = null; // this stays null but shouldn't
Mapper.Map(updateModel, existingStudent);
Assert.AreEqual(777, existingStudent.CollegeId); // passes
Assert.AreEqual(5, existingStudent.StudentPersonal.EthnicityId); // does not pass
Has anyone gotten conditional mapping to work with prefixes? It works ok on the non prefixed object.
The lambda you're passing to opts.Condition is too restrictive:
src => src.PropertyMap.SourceMember != null && src.SourceValue != null
In this property's case, src.PropertyMap is null every time (which you might expect, since there's no single property that maps to the destination nested property from the source object).
If you remove the PropertyMap.SourceMember check, your tests will pass. I'm not sure what impact this will have on the rest of your mapping though.