Comment mapper un nullable propriété à un DTO à l'aide de AutoMapper?
Je suis l'élaboration d'un Azure Mobile Services, dans mon modèle de quelques de la les relations sont en option, rendant les propriétés qui représentent à accepter les valeurs null.
Par exemple, mon Message entité dans mon modèle est la classe comme ceci:
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; }
}
Et mon DTO pour qu'il ressemble à ceci:
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; }
}
Dans mon AutoMapper de configuration, j'ai ceci:
/*
* Mapping for Message entity
*/
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)));
Pour référence, le MySqlFuncs classe a cette fonction pour gérer la conversion de string en int et int en 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();
}
//Can only be used locally.
public static int IntParse(string s)
{
int ret;
int.TryParse(s, out ret);
return ret;
}
}
Alors que je suis en mesure d'insérer des éléments, je ne suis pas en mesure de les obtenir en raison de l'erreur suivante:
Carte manquante de Nullable`1 ' à la Chaîne. Créez à l'aide de
Mappeur.CreateMap<Nullable`1, String>
De ce que je reçois que la ligne resonsible pour cette erreur dans mon AutoMapper définition est:
.ForMember(messageDTO => messageDTO.ParentId, map =>
map.MapFrom(message => message.ParentId))
AutoMapper besoin de savoir comment mapper les valeurs null int de la ParentId propriété dans la DTO. J'ai essayé d'utiliser les fonctions de MySqlFuncs classe:
.ForMember(messageDTO => messageDTO.ParentId, map =>
map.MapFrom(message => MySqlFuncs.LTRIM(MySqlFuncs.StringConvert(message.ParentId))))
Mais qui donne montre l'erreur:
impossible de convertir de 'int?' int'
Si l'on considère la configuration doit être fait de manière à LINQ peut lire un convertir correctement, comment puis-je définir le mappage de sorte que toute nullable propriétés répertoriées dans la chaîne de caractères comme une chaîne vide ("" ou tout simplement la valeur null dans ma DTO?
EDIT 1
Il semble que pour que cela soit résolu, je dois utiliser un ValueResolver, dont j'ai codé comme suit:
public class NullableIntToStringResolver : ValueResolver<int?, string>
{
protected override string ResolveCore(int? source)
{
return !source.HasValue ? "" : MySqlFuncs.LTRIM(MySqlFuncs.StringConvert(source));
}
}
Et changé la cartographie de cette:
cfg.CreateMap<Message, MessageDTO>()
.ForMember(messageDTO => messageDTO.ParentId,
map => map.ResolveUsing(
new NullableIntToStringResolver()).FromMember(message => message.ParentId))
Mais ce qui me donne l'erreur
Objet de référence non définie à une instance d'un objet
Et la StackTrace est-ce:
à
AutoMapper.QueryableExtensions.Extensions.ResolveExpression(PropertyMap
propertyMap, Type currentType, Expression instanceParameter)
au AutoMapper.QueryableExtensions.Extensions.CreateMemberBindings(IMappingEngine
mappingEngine, TypePair typePair, TypeMap typeMap, Expression
instanceParameter, IDictionary`2 typePairCount)
au AutoMapper.QueryableExtensions.Extensions.CreateMapExpression(IMappingEngine
mappingEngine, TypePair typePair, Expression instanceParameter,
IDictionary`2 typePairCount)
au AutoMapper.QueryableExtensions.Extensions.CreateMapExpression(IMappingEngine
mappingEngine, TypePair typePair, IDictionary`2 typePairCount)
au AutoMapper.QueryableExtensions.Extensions.<>c__DisplayClass1`2.b__0(TypePair
tp)
au Système.Les Collections.De façon concomitante.ConcurrentDictionary`2.GetOrAdd(TKey
touche, touche Func`2 valueFactory)
au AutoMapper.Interne.DictionaryFactoryOverride.ConcurrentDictionaryImpl`2.GetOrAdd(TKey
touche, touche Func`2 valueFactory)
au AutoMapper.QueryableExtensions.Extensions.CreateMapExpression[TSource,TDestination](IMappingEngine
mappingEngine)
au AutoMapper.QueryableExtensions.ProjectionExpression`1.ToTResult
au
Microsoft.WindowsAzure.Mobile.Service.MappedEntityDomainManager`2.Query()
chez Microsoft.WindowsAzure.Mobile.Service.TableController`1.Query()
Aucune idée de pourquoi je me fais référence null?
Remarque
Lors du débogage de l'erreur est levée dans mon TableController classe dans le GetAllMessageDTO
méthode:
public class MessageController : TableController<MessageDTO>
{
.
.
.
//GET tables/Message
public IQueryable<MessageDTO> GetAllMessageDTO()
{
return Query(); //Error is triggering here
}
.
.
.
}
Lors du débogage d'aucune des lignes de la cartographie sont accessibles lorsque cette erreur se produit, depuis la cartographie est effectuée lorsque le service est initialisé aussi loin que je peux voir.
- Pour info, même si vous arrive d'être la création d'un Azure Mobile Service, il ne semble pas que votre question (ou les éventuelles réponses) rien avoir à faire avec Azure. Je vous recommande de supprimer cette balise, et de modifier votre texte question de supprimer la référence à Azure.
- La raison pour laquelle vous obtenez le
cannot convert from 'int?' to 'int'
erreur, c'est que votre fonctionStringConvert
prend unint
et pas unint?
. Avez-vous essayé de créer une surcharge de cette méthode qui prend unint?
ou éventuellement à l'aide d'un conditionnel (message.ParentId.HasValue ? : MySqlFuncs.LTRIM(MySqlFuncs.StringConvert(message.ParentId.Value)) : null)
- Je l'ai fait, mais le problème est que lorsque la cartographie de la valeur null à partir de la classe du Modèle qui est un int? à la DTO de la chaîne de propriété, AutoMapper besoin d'une manière de résoudre ce problème. Je vérifie maintenant ValueResolver implémentations, mais j'obtiens le message d'erreur
Object reference not set to an instance of an object
. Vérifier mes mises à jour question. - Une façon simple de le faire ce serait
.ConstructUsing
:Mapper.CreateMap<int?, string>() .ConstructUsing(i => i.HasValue ? i.Value.ToString() : null);
. Cela fonctionne pour vous? - J'ai eu à mettre en œuvre différemment, parce que votre code ne s'applique pas à ces valeurs, pour cette:
cfg.CreateMap<int?, string>().ConstructUsing(i => i.SourceValue != null ? i.SourceValue.ToString() : "");
mais cela lève l'erreurType 'System.String' does not have a default constructor
, de ce que j'ai lu cela signifie que le code est de remplacer le mappage par défaut fournis par AutoMapper, de sorte qu'il shouldln pas être fait. - Débogage et de voir où l'objet est null, probablement
message
dansmessage.ParentId
. - Im placer un point d'arrêt dans cette ligne, mais il n'est jamais atteint, l'exception est levée sur mon TableController de Message sur la
GetAllMessageDTO
méthode sur la lignereturn Query();
Vous devez vous connecter pour publier un commentaire.
Je pense que vous pouvez être en mesure de résoudre ce problème simplement.
Considérons l'exemple suivant:
Le mappage suivant la déclaration de régler comme vous le souhaitez:
Il n'est pas nécessaire pour un ValueResolver dans ce cas, étant donné que votre comportement est très simple - chaîne vide si il n'y a pas de valeur, ou la valeur si elle existe. Au lieu de l'appeler .ToString(), vous pouvez le remplacer par votre StringConvert() la méthode. La chose importante ici est de rendre l'utilisation de l' .HasValue bien sur la Nullable wrapper, et à accès à .La valeur de la propriété quand elle existe. Cela permet d'éviter la complication de l'avoir à les convertir à partir d'int? int.
Pour la conversion de votre persisté chaîne de valeur de retour dans un enum, je vous encourage à explorer cette question: Comment convertir une chaîne en un enum en C#? Vous devriez être en mesure d'utiliser la même logique de mapping.
Ici est une .NET jouer avec plus de détails: https://dotnetfiddle.net/Eq0lof
LINQ to Entities does not recognize the method 'System.String GetName(System.Type, System.Object)' method, and this method cannot be translated into a store expression.
, ai-je raté une cartographie? Je ne sais pas si je dois ouvrir une autre question pour ce problème.Alert = 1
, puis, quand le Type de propriété a une valeur de 1, je veux envoyer la chaîneAlert
sur la DTO. C'est ce dont j'ai besoin.Alert
dans la valeur d'enum quand vous allez de DTO pour votre classe?