2010-01-29 12 views
4

Je viens de commencer à utiliser Entity Framework 1.0 récemment et je crois que je commence à ressentir les douleurs dont tout le monde parle. J'essaie d'utiliser les meilleures pratiques, j'ai donc un ensemble de DTO qui sont mappés vers et depuis mes entités via AutoMapper.Entity Framework, AutoMapper, gestion des mises à jour des entités

La vraie prise est quand j'essaye de mettre à jour un objet. Le premier problème était que je ne pouvais pas trouver un moyen de créer une nouvelle entité, de transférer les données de mon DTO, et que l'entité ObjectContext se rende compte qu'elle a été modifiée. J'ai utilisé le code suivant:

public VideoDTO UpdateVideo(VideoDTO pVideo) 
     { 
      Video video = new Video(); 
      Mapper.Map(pVideo, video); 
      context.Attach(video); //Successfully attaches 
      context.ApplyPropertyChanges("Videos", video); // no changes made as far as entity knows b/c it was attached in it's updated state 
      context.SaveChanges(); //doesn't save the entity     
      return pVideo; 
     } 

J'ai alors pensé que, peut-être que je dois saisir simplement l'entité de la première base de données, joindre au contexte, appelez la méthode de la carte sur Mapper, puis appelez SaveChanges. Voici ce que je faisais:

public VideoDTO UpdateVideo(VideoDTO pVideo) 
    { 
     Video video = context.Videos.Where(v => v.VideoId == pVideo.VideoId).FirstOrDefault(); 
     Mapper.Map(pVideo, video); //Error here: Can't change VideoId value on Video entity 
     //context.Attach(video); 
     //context.ApplyPropertyChanges("Videos", video); 
     context.SaveChanges(); 

     return pVideo; 
    } 

Maintenant, nous arrivons à la question belle EF de ne pas être autorisé à changer la propriété, VideoId, car il est utilisé par la propriété EntityKey sur l'entité de la vidéo. Charmant. J'avais configuré les mappages de sorte que lorsque je mappais de mon DTO à une entité EF, la propriété EntityKey obtenait une valeur. Maintenant, j'ai besoin d'un moyen de faire une exception à cette règle de mappage, mais je n'ai aucune idée par où commencer. Je suppose que je pourrais créer une toute nouvelle règle de mappage directement dans cette méthode et définir les propriétés EntityKey & VideoId à ignorer, mais cela semble plutôt négligé. De plus, je ne suis pas sûr qu'un mappage créé à ce stade tiendrait. Si elle remplaçait la configuration initiale qui permettait au DTO de mapper une valeur à EntityKey sur l'entité, cela se retournerait d'une toute autre manière.

Quelqu'un a-t-il une meilleure idée?

Répondre

6

AutoMapper

Votre premier problème est que, autant que je sache AutoMapper n'a pas été conçu pour aller de DTO-> Entité seule Entité-> DTO. Cela aurait pu changer récemment, donc je ne suis pas vraiment sûr. Voir ce lien pour plus d'informations sur ce automapper est conçu pour faire: The case for two way mapping

PK Mapping

Vous dites: « règle de mappage droit dans cette méthode et définir les EntityKey & propriétés VideoId à ignorer, mais semble assez bâclée "

Je ne pense pas que ce soit bâclé du tout. Vous ne devriez vraiment pas toucher une EntityKey/PK après qu'elle ait été persistée et devrait probablement coder sa stabilité d'une certaine manière.

Entity Framework

« Maintenant, nous arrivons à la question belle EF de ne pas être autorisé à changer la propriété, VideoId, car il est utilisé par la propriété EntityKey sur l'entité de la vidéo. Belle. »

Belle? EF ne vous force pas à ne pas mettre à jour votre PK. À l'intérieur des modèles générés, il y a une vérification de changement de propriété à l'intérieur du setter pour vos clés. La solution serait de changer le code généré. Selon la volatilité de votre modèle, cela peut ne pas être pratique mais c'est une option.

+0

jfar: Merci pour votre réponse. Tout d'abord, à quoi faites-vous référence par AFAIK AutoMapper? Je fais référence à l'AutoMapper ici: http://code.google.com/p/automapperhome/. En ce qui concerne le PK Mapping, je parle de définir tous les mappages en un seul endroit et de ne pas encombrer mon code avec des instructions .CreateMap dans tout le code. Le problème ici est que je dois ma carte de manière circonstancielle mon DTO à mon Entité de différentes manières selon si je crée un nouvel enregistrement dans mon DB ou mettre à jour un enregistrement dans mon DB. Plus sur le AutoMapper lui-même dans une seconde ... à court de caractères ... – jason

+1

D'accord, vous avez mentionné que Automapper n'était pas destiné à aller de DTO -> EF Entity, mais était destiné à être EF -> DTO seulement. Si c'était le cas, comment dois-je mettre à jour ma base de données via des objets EF lorsque les objets utilisés dans ma vue et dans mon contrôleur sont des objets DTO? Je n'essaie pas d'être condescendant ou quoi que ce soit. J'essaye juste de trouver la bonne manière de manipuler des insertions et des mises à jour, tout en ne liant pas mon application à une implémentation spécifique d'accès de données. Je suis ouvert à toutes les suggestions que vous pourriez avoir. – jason

+0

AFAIK = pour autant que je sache, désolé de ne pas être plus clair – jfar

0

Je suis dans le même scénario. La seule solution que j'ai eu est d'ignorer le champ PK dans le mapping de DTO -> Entity.

Une telle règle peut être atteint par la ligne de code suivante lors de la configuration Automapper:

Mapper.CreateMap<MyDTO, MyEntity>().ForMember("EntityPK",r=>r.Ignore()); 

Pour autant que je sache, la seule façon d'obtenir EF fonctionne avec les entités Détaché cartographie les DTO à l'entité vous avez obtenu de DB avant le SaveChanges (comme vous l'avez fait dans l'exemple).

1

Cela peut aider si vous voulez éviter de mettre .Ignore() s sur toutes les entités que vous voulez à la carte.

http://www.prosoftnearshore.com/blog/post/2012/03/14/Using-AutoMapper-to-update-Entity-Framework-properties.aspx

Essentiellement, vous souhaitez configurer AutoMapper d'ignorer toutes les propriétés des entités qui ne sont pas scalaire:

AutoMapper.Mapper.CreateMap<EntityType, EntityType>() 
    .ForAllMembers(o => { 
     o.Condition(ctx => 
      { 
       var members = ctx.Parent.SourceType.GetMember(ctx.MemberName); // get the MemberInfo that we are mapping 

       if (!members.Any()) 
        return false; 
       return members.First().GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false).Any(); // determine if the Member has the EdmScalar attribute set 
      }); 
    }); 

Peut-être un peu de travail supplémentaire peut être ajouté pour éviter la réinitialisation si la propriété est un PK (une propriété dans l'instance EdmScalarPropertyAttribute (EntityKey == true?) vous le dit).

0

Veuillez noter que l'exemple fourni par "Mauricio Morales" ne fonctionnera que si vous n'utilisez pas de préfixe. Si vous les utilisez, vous devez changer le code ci-dessus légèrement façon plus ou moins comme ceci:

Mapper.CreateMap<tempOR_Order, OR_Order>() 
     .ForMember(m => m.OR_ID, exp => exp.Ignore()) 
     .ForMember(m => m.OR_CU_ID, exp => exp.Ignore()) 
     .ForAllMembers(o => o.Condition(ctx => 
     { 
      var members = ctx.Parent.SourceType.GetMember(ctx.MemberName); // get the MemberInfo that we are mapping 

      if (!members.Any()) 
      { 
       members = ctx.Parent.SourceType.GetMember("temp" + ctx.MemberName); 
       if (!members.Any()) 
        return false; 
      } 

      return members.First().GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false).Any(); // determine if the Member has the EdmScalar attribute set 
     })); 

C'est, vous devez inclure des contrôles supplémentaires à l'intérieur if (!members.Any()) déclaration. Sans cela, la fonction renvoie false et le mappage ne fonctionnera pas.