Entity Framework - Doublon dans la DB

Entity Framework - Doublon dans la DB - C#/.NET managed - Programmation

Marsh Posté le 10-10-2015 à 14:33:59    

Bonjour
 
J'essaye de me faire un peu la main sur Entity Framkework. l'API a un comportement pour le moins bizarre, ou du moins que je ne comprends pas. Du coup je me demande si je ne me serais pas trompé quelque part.
 
J'explique.
 
Imaginez que vous ayez une entity "Contact" et une "Type". Un contact peut avoir un type. Et un type peut être associé à plusieurs contacts, forcément.
 
Alors ce que je ne comprends pas c'est que quand j'associe un objet "contact" avec un objet "type" déjà présent en db (bref qui a déjà un id), EF injecte un contact en base de donnés, très bien, sauf qu'en même temps il me réinjecte un nouveau objet "Type" en base de donnés aussi (alors qu'il y est déjà présent!).
 
Je me suis trompé quelque part dans la config du bazar?
 
Merci

Reply

Marsh Posté le 10-10-2015 à 14:33:59   

Reply

Marsh Posté le 13-10-2015 à 13:54:36    

La question est : comment tu lis ton type existant et ton nouveau contact dans ton code.
Commence par montrer comment tu écris dans ta base, je ne vois pas comment on pourrait t'aider à corriger ton code sans le voir :sarcastic:...


Message édité par TotalRecall le 13-10-2015 à 13:55:12

---------------
Réalisation amplis classe D / T      Topic .Net - C# @ Prog
Reply

Marsh Posté le 14-10-2015 à 16:15:46    

C'est basé en grande partie sur cette DAL:
http://dotnetspeak.com/2013/03/vs- [...] as-vegas-2

 


Mais je crois que je viens de comprendre d'où viens le problème, et ça me fout les boules.
Je ne sais pas pourquoi sur sa démo à lui en EF5 ca marche, alors que moi en EF6 ça ne marche pas, alors que je n'ai pas fait de grosse différence au niveau du code.

 


Je me rends compte en fait que dès qu'on associe un objet à un autre, il faut obligatoirement le faire au sain d'un context. (using...).
Si les objets sont associés en-dehors du context ou dans 2 context différents, le lien ne se fait pas correctement.

 

Mais du coup comment on fait pour ne pas utiliser de context dans la couche Business???


Message édité par fredo3 le 14-10-2015 à 16:21:47
Reply

Marsh Posté le 14-10-2015 à 16:17:26    

Je ne comprend pas la moitié de ce que tu dis.
On veut voir TON code, la partie utile. Vu ton use case décrit au début ça ne doit pas faire plus de 30 lignes.


---------------
Réalisation amplis classe D / T      Topic .Net - C# @ Prog
Reply

Marsh Posté le 14-10-2015 à 16:22:21    

Il y a une vingtaine de classes et dizaine d'interfaces pour la DAL... alors bon...


Message édité par fredo3 le 14-10-2015 à 16:22:30
Reply

Marsh Posté le 14-10-2015 à 16:39:12    

Y a pas 50000 façons de charger ou désigner une entité existante par son id, pour la lier à une autre et déclencher la persistance.  
C'est ça dont on a besoin pour t'aider, pas du brol autour.  
Perso là j'ai pas trop envie de debugger 16,5Mo de sources d'un autre qui ne posent même pas le problème que tu espères nous voir résoudre...


---------------
Réalisation amplis classe D / T      Topic .Net - C# @ Prog
Reply

Marsh Posté le 14-10-2015 à 18:09:42    

Bon okay.
 
Alors dans la couche Access layer, pour tous ce qui est d'opération d'écriture il y a cette classe abstraite "WriteRepository":

Code :
  1. public abstract class WriteRepository<TContext> : IWriteRepository where TContext : DbContext, new()
  2.     {
  3.         private readonly TContext _context;
  4.         protected TContext Context { get { return _context; } }
  5.         protected WriteRepository()
  6.         {
  7.             _context = new TContext();
  8.         }
  9.         public void Dispose()
  10.         {
  11.             _context.Dispose();
  12.         }
  13.         public TItem Update<TItem>(TItem item, bool saveImmediately = true) where TItem : class, new()
  14.         {
  15.             return PerformAction(item, EntityState.Modified, saveImmediately);
  16.         }
  17.         public TItem Delete<TItem>(TItem item, bool saveImmediately = true) where TItem : class, new()
  18.         {
  19.             return PerformAction(item, EntityState.Deleted, saveImmediately);
  20.         }
  21.         public TItem Insert<TItem>(TItem item, bool saveImmediately = true) where TItem : class, new()
  22.         {
  23.             return PerformAction(item, EntityState.Added, saveImmediately);
  24.         }
  25.         public void Save()
  26.         {
  27.             _context.SaveChanges();
  28.         }
  29.         protected virtual TItem PerformAction<TItem>(TItem item, EntityState entityState, bool saveImmediately = true) where TItem : class, new()
  30.         {
  31.             _context.Entry(item).State = entityState;
  32.             if (saveImmediately)
  33.             {
  34.                 _context.SaveChanges();
  35.             }
  36.             return item;
  37.         }


 
Elle est abstraite, générique et est implémentée par tous les classes repositories. Exemple celle des catégories:

Code :
  1. public class CategoryRepository : WriteRepository<AppContext>, ICategoryRepository
  2.     {
  3.         public IEnumerable<Category> GetCategories(ICriteria criteria)
  4.         {
  5.             IQueryable<Category> query = Context.Categories;
  6.             if (criteria.IsSearch)
  7.             {
  8.                 var value = criteria.GetFieldData(criteria.FilterColumn);
  9.                 query = query.Where(one => one.Name.Contains(value));
  10.             }
  11.             if (criteria.SortColumn == "Name" && criteria.SortOrder == "asc" )
  12.             {
  13.                 query = query.OrderBy(one => one.Name);
  14.             }
  15.             else if (criteria.SortColumn == "Name" && criteria.SortOrder == "desc" )
  16.             {
  17.                 query = query.OrderByDescending(one => one.Name);
  18.             }
  19.             else
  20.                 query = query.OrderBy(one => one.Name);
  21.             query = query.Skip((criteria.PageIndex - 1) * criteria.PageSize).Take(criteria.PageSize);
  22.             return query;
  23.         }
  24.         public int GetTotalCategories()
  25.         {
  26.             return Context.Categories.Count();
  27.         }
  28.         public Category GetCategoryById(int id)
  29.         {
  30.             return Context.Categories.FirstOrDefault(one => one.CategoryId == id);
  31.         }
  32.     }

 
Comme tu le vois cette classe repository dispose de méthodes permettant de charger des objets présents en db.
 
Au dessus de tout ça, il y la couche Business qui dispose de classes de service. Ces classes de service sont instanciées à l'aide de StructureMap pour une meilleure isolation entre les couches. Mais rien de spécial de ce côté elles ne font que faire appel aux méthodes des classes repository correspondantes.
 
Bref en gros l'opération que j'exécute pour vérifier si l'association entre l'object "Contact" et l'objet "Category" se fait bien est la suivante:

Code :
  1. Data.Category cat = new Data.Category() { Name = "My new Category" };
  2.             CategoryService catService = new CategoryService();
  3.             catService.CreateCategory(cat);
  4.             //cat = catService.GetCategory(1);
  5.             Data.Contact contact = new Data.Contact { Name = "test name 1" };
  6.             contact.Category = cat;
  7.             ContactService contactService = new ContactService();
  8.             contactService.CreateContact(contact);


 
Résultat, la cat est présente 2 fois en db.
 
Alors que si je n'utilise pas la DAL, et que j'utilise le classique:

Code :
  1. using (var cxt = new AppContext())
  2.             {
  3. ...
  4. ...
  5.             }


Et que j’effectue les opérations dedans, ça marche correctement.
Alors il y a bien la méthode "Attache" du context qui permet justement de rattacher un objet déconnecté du context. Mais ça fait limite usine à case par la suite.
Il y a aussi la méthode "AddOrUpdate", mais elle effectue une opération de lecture en db à chaque fois pour vérifier si l'objet est bien présent ou pas, bref usine à gaz aussi.


Message édité par fredo3 le 14-10-2015 à 18:30:12
Reply

Marsh Posté le 15-10-2015 à 09:10:41    

Au début de ton code tu as déjà ta catégorie en base, ou bien tu en as 0 ?

 

Si elle existe déjà et que Name sur catégorie n'est pas ta PK comment tu veux que le système comprenne qu'il doit prendre celle qui existe et pas en générer une nouvelle ?
Pour lui tu es bien en train de chercher à créer une nouvelle catégorie. Pour attacher une existante il faut la charger par son id, ou bien écrire directement dans le champ FK_Categorie de Contact.

 

Si par contre tu dis que ce code là te génère 2 catégories alors que tu en avais 0, je ne sais quoi te dire.


Message édité par TotalRecall le 15-10-2015 à 09:11:06

---------------
Réalisation amplis classe D / T      Topic .Net - C# @ Prog
Reply

Marsh Posté le 15-10-2015 à 11:46:02    

La méthode "CreateCategory" en ligne 3, crée justement l'objet en base de donnée.
Avant cette ligne son id est bien évidemment 0, après le CreateCategory l'id est bien incrémenté.

 

Mais même sans créer l'objet, par exemple en utilisant à la place la ligne 5 en commentaire "catService.GetCategory(1);", un objet existant en db est chargé au lieu de le créer, pourtant le problème est le même.

Message cité 1 fois
Message édité par fredo3 le 15-10-2015 à 11:46:56
Reply

Marsh Posté le 15-10-2015 à 12:25:04    

fredo3 a écrit :


Mais même sans créer l'objet, par exemple en utilisant à la place la ligne 5 en commentaire "catService.GetCategory(1);", un objet existant en db est chargé au lieu de le créer, pourtant le problème est le même.


C'est pas clair pour moi :pt1cable:
T'es en train de me dire qu'en liant cette instance d'objet chargé depuis la base à ton contact tu te retrouves avec une nouvelle catégorie ? Et avec un id différent dans ce cas ? :heink:
pas possible :pt1cable:


Message édité par TotalRecall le 15-10-2015 à 12:25:44

---------------
Réalisation amplis classe D / T      Topic .Net - C# @ Prog
Reply

Marsh Posté le 15-10-2015 à 12:25:04   

Reply

Marsh Posté le 15-10-2015 à 12:27:40    

Après sauvegarde du contact, oui c'est ça :D

 

En gros j'associe au contact la catégorie qui a un ID = 1. Je sauvegarde le contact, et il se retrouve avec une catégorie avec ID = 2 :D.


Message édité par fredo3 le 15-10-2015 à 12:28:16
Reply

Marsh Posté le 15-10-2015 à 14:11:21    

Poste le code de la version qui est sensé faire ça avec le GetCategory(1).


---------------
Réalisation amplis classe D / T      Topic .Net - C# @ Prog
Reply

Marsh Posté le 26-10-2015 à 16:49:20    

J'ai pas tout compris non plus.
 
Je soupçonne que tu essaies d'assigner un objet Type à ton Contact au lieu d'assigner uniquement l'ID. (j'ai plus fait d'EF depuis 11 mois mais il me semble que j'avais fait une erreur similaire au tout début :D )
 
Genre:
 

Code :
  1. Contact.Type = Type;


 
au lieu de  
 

Code :
  1. Contact.TypeId = Type.TypeId;



---------------
Whichever format the fan may want to listen is fine with us – vinyl, wax cylinders, shellac, 8-track, iPod, cloud storage, cranial implants – just as long as it’s loud and rockin' (Billy Gibbons, ZZ Top)
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed