why my Null substitution is not working in automapper - c#

I have written a programme in c# and used automapper to map.I have used Null substitution but it is not substituting the given value in result i am getting null only.please check the below code.I want my result to show 8099000078.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Automapper;
namespace Automapper
{
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string Mobile { get; set; }
}
public class Patient
{
public string Name { get; set; }
public string Location { get; set; }
public int age { get; set; }
public string phone { get; set; }
static void Main(string[] args)
{
User obj = new User();
obj.FirstName = "sujit";
obj.LastName = "kumar";
obj.Address = "Bangalore";
obj.Mobile = "";
AutoMapper.Mapper.CreateMap<User, Patient>().ForMember(
emp => emp.Name, map => map.MapFrom(p => p.FirstName + " " + p.LastName))
.ForMember(dest => dest.Location, source => source.MapFrom(x => x.Address))
.ForMember(dest => dest.phone, source => source.NullSubstitute("8099000078"));
var result = AutoMapper.Mapper.Map<User, Patient>(obj);
// Console.WriteLine(result.Name);
// Console.WriteLine(result.Location);
Console.WriteLine(result.phone);
Console.ReadLine();
}
}
}

You missed how null substitute works.
From AutoMapper Wiki:
Null substitution allows you to supply an alternate value for a
destination member if the source value is null anywhere along the
member chain.
In your code you didn't provide any source for phone property, therefore it doesn't have any value to check on null and it does not perform null substitution.
I made some changes in your code to make it works (see below):
void Main()
{
User obj = new User();
obj.FirstName = "sujit";
obj.LastName = "kumar";
obj.Address = "Bangalore";
obj.Mobile = null;
var config = new MapperConfiguration(cfg => cfg.CreateMap<User, Patient>()
.ForMember(emp => emp.Name, map => map.MapFrom(p => p.FirstName + " " + p.LastName))
.ForMember(dest => dest.Location, source => source.MapFrom(x => x.Address))
.ForMember(dest => dest.phone, source => source.MapFrom(x => x.Mobile))
.ForMember(dest => dest.phone, source => source.NullSubstitute("8099000078")));
var mapper = config.CreateMapper();
var result = mapper.Map<User, Patient>(obj);
result.Dump();
}
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string Mobile { get; set; }
}
public class Patient
{
public string Name { get; set; }
public string Location { get; set; }
public int age { get; set; }
public string phone { get; set; }
}
Here I provided a source for phone property (I mapped it from Mobile) and set the null value.

This should work -
.ForMember(dest => dest.phone, source => source.MapFrom(a => !string.IsNullOrWhiteSpace(a.Mobile) ? a.Mobile : "8099000078"));

I've not used Automapper before, but looking at your syntax it appears that 'source' would never be null because you pass it a valid obj (thus never having the NullSubstitute() return 8099000078.
I don't see where you're trying to map dest => dest.phone, source => source.Mobile.

Related

LINQ with dynamic group by

I have model:
public class Student
{
public string Name{ get; set; }
public DateTime BirthDate{ get; set; }
public string UniversityName{ get; set; }
public decimal Balance{ get; set; }
}
I have three bool variables:
IsName
IsBirthDate
IsUniversityName
And based on them, I need to create GroupBy. If IsBirthDate=true then
DbContext.Students.GroupBy(x => new { x.BirthDate }).Select(s => new
{
s.Key.BirthDate,
Balance = s.Sum(x => x.Balance).ToString(),
});
If IsBirthdate=true, IsUniversityName=true then
DbContext.Students.GroupBy(x => new { x.BirthDate, x.UniversityName }).Select(s => new
{
s.Key.BirthDate,
s.Key.UniversityName,
Balance = s.Sum(x => x.Balance).ToString(),
});
And other options with bool parameters.
How to generate the query dynamically with .GroupBy and .Select?
Maybe this helps you: Create a class that represents the key for your GroupBy:
public class StudentGroupingKey
{
public string Name { get; set; }
public DateTime? BirthDate{ get; set; }
public string UniversityName { get; set; }
}
If IsName is true, the Name property of the grouping key will be the value, otherwise it should have always the same value (e.g. null). In your Select method, you have to have always every property, but they will be null if the corresponding properties will be false. If it is necessary for you that these properties do not exist, you can't work with anoymous types. Maybe dynamic might be an option in that case.
DbContext.Students.GroupBy(x => new StudentGroupingKey
{
Name = IsName ? x.Name : null,
BirthDate = IsBirthDate ? x.Birthdate : null,
UniversityName = IsUniversityName ? x.UniversityName : null
}).Select(s => new
{
s.Key.BirthDate,
s.Key.Name,
s.Key.UniversityName,
Balance = s.Sum(x => x.Balance).ToString()
});

Automapper suddenly creates nested object

Entities:
public class Entity
{
public int Id { get; set; }
}
public class User : Entity
{
public string Name { get; set; }
public Company Company { get; set; }
}
public class Company : Entity
{
public string Name { get; set; }
}
Dto's:
public class EntityDto
{
public int Id { get; set; }
}
public class UserDto : EntityDto
{
public string Name { get; set; }
public int? CompanyId { get; set; }
}
So I want to map User to UserDto like User.Company == null => UserDto.CompanyId == null and vice versa.
That is my Automapper configuration:
Mapper.Initialize(configuration =>
{
configuration
.CreateMap<User, UserDto>()
.ReverseMap();
});
This works fine:
[Fact]
public void UnattachedUserMapTest()
{
// Arrange
var user = new User { Company = null };
// Act
var userDto = Mapper.Map<User, UserDto>(user);
// Assert
userDto.CompanyId.Should().BeNull();
}
but this test fails:
[Fact]
public void UnattachedUserDtoMapTest()
{
// Arrange
var userDto = new UserDto { CompanyId = null };
// Act
var user = Mapper.Map<UserDto, User>(userDto);
// Assert
user.Company.Should().BeNull();
}
Details:
Expected object to be <null>, but found
Company
{
Id = 0
Name = <null>
}
Doesn't work for me:
...
.ReverseMap()
.ForMember(user => user.Company, opt => opt.Condition(dto => dto.CompanyId != null));
and well as that (just for example):
...
.ReverseMap()
.ForMember(user => user.Company, opt => opt.Ignore());
Why does Automapper create nested object and how can I prevent it?
That "suddenly" bit is funny :)
configuration.CreateMap<User, UserDto>().ReverseMap().ForPath(c=>c.Company.Id, o=>o.Ignore());
You have a default MapFrom with CompanyId and that is applied in reverse. For details see this and a few other similar issues.
In the next version (on MyGet at the moment) you'll also be able to use
configuration.CreateMap<User, UserDto>().ReverseMap().ForMember(c=>c.Company, o=>o.Ignore());

AutoMapper Conditional Mapping Not Working With Skipping Null Destination Values

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.

How can I compare two classes base their methods?

I want to find out the missing properties in one class by comparing the other class
public class User
{
public int UserID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class UserDTO
{
public string UserName { get; set; }
public string FirstName { get; set; }
}
Above I should get the output like "UserID, "LastName" properties are missing in UserDTO.
var list = typeof(User).GetProperties().Select(x => x.Name)
.Except(typeof(UserDTO).GetProperties().Select(y => y.Name))
.ToList();
EDIT
Including suggestions in comments and public Fields
public static IEnumerable<string> Diff(Type t1, Type t2)
{
return t1.GetProperties().Select(p1 => new { Name = p1.Name, Type = p1.PropertyType })
.Concat(t1.GetFields().Select(f1 => new { Name = f1.Name, Type = f1.FieldType }))
.Except(t2.GetProperties().Select(p2 => new { Name = p2.Name, Type = p2.PropertyType })
.Concat(t2.GetFields().Select(f2 => new { Name = f2.Name, Type = f2.FieldType })))
.Select(a => a.Name);
}
Use reflection to get the properties, see Type.GetProperties. Then compare both property lists to find the missing ones.
var UserProperties = typeof(User).GetProperties().Select(p => p.Name);
var UserDTOProperties = typeof(UserDTO).GetProperties().Select(p => p.Name);
var missingProperties = UserProperties.Except(UserDTOProperties);
Take into account that all inherited properties will also be present in these lists, unless yous specify BindingFlags.DeclaredOnly to the GetProperties() method, see BindingFlags.

Mapping "LinkedList" with AutoMapper

I have linked list kind of situation. My DTO looks like this -
public class DTOItem
{
public string ID
{
get;
set;
}
public int? UniqueId
{
get;
set;
}
public string Payload
{
get;
set;
}
//How do I map this guy? It is list of same type.
public List<DTOItem> RelatedItems
{
get;
set;
}
}
How do I map this guy using AutoMapper? I am able to map other members of the class. Data is mapped from another class' collection object that has a different set of member not identical to this class.
public List<DTOItem> RelatedItems
{
get;
set;
}
Thanks in advance.
UPDATE: Here is the code -
Raphael, here is the code:
The Source Objects:
public class ResultsSet
{
public int? ResultId
{
get;
set;
}
public int UID
{
get;
set;
}
//Returns large XML string
public string ResultBlob
{
get;
set;
}
public RelatedItems[] RelatedSet
{
get;
set;
}
}
public class RelatedItems
{
public int Item_ID
{
get;
set;
}
public int Relationship_ID
{
get;
set;
}
public string Description
{
get;
set;
}
}
To map here is the code:
Mapper.CreateMap<ResultSet, DTOItem>()
.ForMember(dest => dest.ID, opt => opt.MapFrom(src => src.ResultID.GetValueOrDefault(0)))
.ForMember(dest => dest.UniqueId, opt => opt.MapFrom(src => src.UID))
.ForMember(dest => dest.Payload, opt => opt.MapFrom(src => src.ResultBlob));
/*
How do I map RelatedSet to RelatedItems here?
*/
Mapper.Map(result, returnResult);
Thanks again.
No need to use AutoMapper for this.
For non-cyclic, relatively flat data, this should do:
static Func<RelatedItems, DTOItem> MapRelated(IEnumerable<ResultsSet> all) {
var map = MapResultSet(all);
return relatedItem => map(all.First(x => x.UID == relatedItem.Item_ID));
}
static Func<ResultsSet, DTOItem> MapResultSet(IEnumerable<ResultsSet> all) {
return s =>
new DTOItem {
ID = s.ResultId.GetOrElse(0).ToString(),
UniqueId = s.UID,
Payload = s.ResultBlob,
RelatedItems = (s.RelatedSet ?? new RelatedItems[0]).Select(MapRelated(all)).ToList()
};
}
Sample usage:
var data = new[] {
new ResultsSet {
UID = 1,
RelatedSet = new[] {
new RelatedItems { Item_ID = 2 },
new RelatedItems { Item_ID = 3 },
},
},
new ResultsSet {
UID = 2,
},
new ResultsSet {
UID = 3,
},
};
var items = data.Select(MapResultSet(data)).ToList();
Debug.Assert(items.Count == 3);
Debug.Assert(items[0].UniqueId == 1);
Debug.Assert(items[1].UniqueId == 2);
Debug.Assert(items[2].UniqueId == 3);
Debug.Assert(items[0].RelatedItems.Count == 2);
Debug.Assert(items[0].RelatedItems[0].UniqueId == items[1].UniqueId);
Debug.Assert(items[0].RelatedItems[1].UniqueId == items[2].UniqueId);
I assumed Item_ID is the 'key' to UID, otherwise simply adjust MapRelated.
Generally speaking, I think AutoMapper is only useful if you have to map untyped data into typed data, and even in that case I'd think really hard before using it. Otherwise, some LINQ code is simpler and more type safe.

Categories