I am developing the Azure Mobile Service, in my model some of these relationships are optional, which makes the properties that represent it may be null.
For example, my Message object in my model class looks like this:
public partial class Message { public Message() { this.Messages = new HashSet<Message>(); } public int Id { get; set; } public int CreatedById { get; set; } public int RecipientId { get; set; } public Nullable<int> ParentId { get; set; } public string Title { get; set; } public string Content { get; set; } public int MessageTypeId { get; set; } public Nullable<MessageType> Type { get; set; } public Nullable<bool> Draft { get; set; } public Nullable<bool> Read { get; set; } public Nullable<bool> Replied { get; set; } public Nullable<bool> isDeleted { get; set; } [JsonIgnore] [ForeignKey("CreatedById")] public virtual User CreatedBy { get; set; } [JsonIgnore] [ForeignKey("RecipientId")] public virtual User Recipient { get; set; } [JsonIgnore] public virtual ICollection<Message> Messages { get; set; } [JsonIgnore] public virtual Message Parent { get; set; } }
And my DTO for him looks like this:
public class MessageDTO : EntityData { public string CreatedById { get; set; } public string RecipientId { get; set; } public string ParentId { get; set; } public string Title { get; set; } public string Content { get; set; } public string Type { get; set; } public Nullable<bool> Draft { get; set; } public Nullable<bool> Read { get; set; } public Nullable<bool> Replied { get; set; } public Nullable<bool> isDeleted { get; set; } }
In my AutoMapper configuration, I have the following:
cfg.CreateMap<Message, MessageDTO>() .ForMember(messageDTO => messageDTO.Id, map => map.MapFrom(message => MySqlFuncs.LTRIM(MySqlFuncs.StringConvert(message.Id)))) .ForMember(messageDTO => messageDTO.ParentId, map => map.MapFrom(message => message.ParentId)) .ForMember(messageDTO => messageDTO.CreatedById, map => map.MapFrom(message => MySqlFuncs.LTRIM(MySqlFuncs.StringConvert(message.CreatedById)))) .ForMember(messageDTO => messageDTO.RecipientId, map => map.MapFrom(message => MySqlFuncs.LTRIM(MySqlFuncs.StringConvert(message.RecipientId)))) .ForMember(messageDTO => messageDTO.Type, map => map.MapFrom(message => Enum.GetName(typeof(MessageType), message.MessageTypeId))); cfg.CreateMap<MessageDTO, Message>() .ForMember(message => message.Id, map => map.MapFrom(messageDTO => MySqlFuncs.IntParse(messageDTO.Id))) .ForMember(message => message.ParentId, map => map.MapFrom(messageDTO => messageDTO.ParentId)) .ForMember(message => message.CreatedById, map => map.MapFrom(messageDTO => MySqlFuncs.IntParse(messageDTO.CreatedById))) .ForMember(message => message.RecipientId, map => map.MapFrom(messageDTO => MySqlFuncs.IntParse(messageDTO.RecipientId)));
For reference, the MySqlFuncs class has these functions for handling conversion from string to int and int to string:
class MySqlFuncs { [DbFunction("SqlServer", "STR")] public static string StringConvert(int number) { return number.ToString(); } [DbFunction("SqlServer", "LTRIM")] public static string LTRIM(string s) { return s == null ? null : s.TrimStart(); }
While I can insert elements, I cannot receive them due to an error:
Missing map from Nullable`1 to String. Create Using Mapper.CreateMap
From this, I understand that the line responsible for this error in my AutoMapper definition is as follows:
.ForMember(messageDTO => messageDTO.ParentId, map => map.MapFrom(message => message.ParentId))
AutoMapper needs to know how to map a nullable int from the ParentId property in the DTO. I tried using functions from the MySqlFuncs class:
.ForMember(messageDTO => messageDTO.ParentId, map => map.MapFrom(message => MySqlFuncs.LTRIM(MySqlFuncs.StringConvert(message.ParentId))))
But this gives an error:
can't convert from 'int?' to 'int'
If we consider that the configuration should be done in such a way that LINQ can read it correctly, how can I define the mapping so that any properties with a null value are displayed as a string as an empty string "" or just null in my DTO?
EDIT 1
It seems that for this I should use a ValueResolver, which I encoded as follows:
public class NullableIntToStringResolver : ValueResolver<int?, string> { protected override string ResolveCore(int? source) { return !source.HasValue ? "" : MySqlFuncs.LTRIM(MySqlFuncs.StringConvert(source)); } }
And changed the mapping to this:
cfg.CreateMap<Message, MessageDTO>() .ForMember(messageDTO => messageDTO.ParentId, map => map.ResolveUsing( new NullableIntToStringResolver()).FromMember(message => message.ParentId))
But it gives me an error
Object reference not set to object instance
And StackTrace:
in AutoMapper.QueryableExtensions.Extensions.ResolveExpression (PropertyMap propertyMap, type currentType, Expression instanceParameter)
in AutoMapper.QueryableExtensions.Extensions.CreateMemberBindings (IMappingEngine mappingEngine, TypePair typePair, TypeMap typeMap, Expression instanceParameter, IDictionary`2 typePairCount)
in AutoMapper.QueryableExtensions.Extensions.CreateMapExpression (IMappingEngine mappingEngine, TypePair typePair, Expression instanceParameter, IDictionary`2 typePairCount)
in AutoMapper.QueryableExtensions.Extensions.CreateMapExpression (IMappingEngine mappingEngine, TypePair typePair, IDictionary`2 typePairCount)
in AutoMapper.QueryableExtensions.Extensions. <> c__DisplayClass1`2.b__0 (TypePair f)
in System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd (TKey key, Func`2 valueFactory)
in AutoMapper.Internal.DictionaryFactoryOverride.ConcurrentDictionaryImpl`2.GetOrAdd (TKey key, Func`2 valueFactory)
in AutoMapper.QueryableExtensions.Extensions.CreateMapExpression [TSource, TDestination] (IMappingEngine mappingEngine)
in AutoMapper.QueryableExtensions.ProjectionExpression`1.ToTResult
in Microsoft.WindowsAzure.Mobile.Service.MappedEntityDomainManager`2.Query () in Microsoft.WindowsAzure.Mobile.Service.TableController`1.Query ()
Any idea why I am getting a null link?
Note
When error debugging is called in my TableController class in the GetAllMessageDTO method:
public class MessageController : TableController<MessageDTO> { . . . // GET tables/Message public IQueryable<MessageDTO> GetAllMessageDTO() { return Query(); // Error is triggering here } . . . }
When debugging, none of the matching lines are opened when this error occurs, since the mapping is performed when the service is initialized, as far as I can see.