FunnyBean / IntraWeb

0 stars 0 forks source link

Správa užívateľov - Web api #57

Closed Burgyn closed 8 years ago

Burgyn commented 8 years ago

Používateľské rozhranie sa rieši v #18

Otázky Čo všetko ideme o používateľovi evidovať? (email / meno / priezvisko / foto / ?)

majco333 commented 8 years ago

Zatiaľ asi stačia tieto 4 údaje. Ako si myslel toto? "Mať to pripravené tak aby sa z firemných kontaktoch dali odoslať dáta"

Burgyn commented 8 years ago

Pre zjednodušenie správy používateľov, chceme aby sa dali z našich kontaktov (firemná appka, čo máme) jednoducho odoslať do intrawebu. Aby nemusela prevádzka nahadzovať ľudí do kontaktov a následne do intrawebu.

Tak aby to web api umožňovalo. Následne ja potom dorobím do kontaktov daný export.

majco333 commented 8 years ago

To chapem, ale ze ci to pripravim, ze to pride cez json ako zoznam userov? Alebo inak?

Burgyn commented 8 years ago

Asi tak. Ešte som sa nad tým nezamýšľal :-)

majco333 commented 8 years ago

Esta otazka k fotkam - predpokladam, ze v kontaktoch nie su verejne dostupne (nebude stacit si poslat link a z linku si obrazok stiahnut)?

Bude treba ich preniest cez JSON (ak to vobec ide - ale nasiel som, ze mozno hej cez base64) alebo inym sposobom...

Burgyn commented 8 years ago

Tak ako hovoríš, fotky niesu dostupné cez link. Takže ich tam bude potrebné nejakým spôsobom poslať. To ako ich tam posielať, tak ti teraz neviem povedať.

Ak sa ti to nepodarí, tak môžem skúsiť naštudovať. On 28 Feb 2016 07:56, "Majo Mikula" notifications@github.com wrote:

Esta otazka k fotkam - predpokladam, ze v kontaktoch nie su verejne dostupne (nebude stacit si poslat link a z linku si obrazok stiahnut)?

Bude treba ich preniest cez JSON (ak to vobec ide - ale nasiel som, ze mozno hej cez base64) alebo inym sposobom...

— Reply to this email directly or view it on GitHub https://github.com/FunnyBean/IntraWeb/issues/57#issuecomment-189803829.

majco333 commented 8 years ago

Chalani potreboval by som poradit. Dost dlho som sa trapil s aktualizaciou roli pri aktualizacii uzivatela. Nakoniec to mam funkcne, ale nepozdava sa mi to (musi to ist aj lepsim, krajsim sposobom...). Musel som najprv zaktualizovat udaje uzivatela bez roli, potom pouzit asNoTracking a nakoniec zaktualizovat role - viete ako to urobit lepsie?

Toto je kod pri zavolani aktualizacie udajov uzivatela spolu s jeho rolami (je to aj commitnute v mojom branchi):

IActionResult result;
User oldUser = _userRepository.GetItem(userId);
IEnumerable<int> newRolesIds = (userVm.UserRoles ?? new List<UserRoleViewModel>()).Select(x => x.RoleId).AsEnumerable();

// Updating of user values
userVm.UserRoles = null; // It's important for disable duplicating of roles
User editedUser = AutoMapper.Mapper.Map(userVm, oldUser);

result = SaveData(() =>
{
    _userRepository.Edit(editedUser);
});

// Updating of role values
IEnumerable<int> oldRolesIds = _userRepository.GetItemIncluding(userId, true, x => x.UserRoles).UserRoles.Select(x => x.RoleId).AsEnumerable();

if (!Enumerable.SequenceEqual(oldRolesIds, newRolesIds)) // Are role Ids the same?
{
    _userRoleRepository.Delete(x => x.UserId == oldUser.Id); // Remove old roles (new roles will be added later)
    _userRoleRepository.Save();

    foreach (int roleId in newRolesIds)
    {
        _userRoleRepository.Add(new UserRole()
        {
            RoleId = roleId,
            UserId = oldUser.Id
        });
    }

    _userRoleRepository.Save();
}

return result;
majco333 commented 8 years ago

Treba to viac objasnit? Alebo prejdeme na stretku?

satano commented 8 years ago

Na stretku. Už tu moc nestihneme do zajtra. :smile:

Burgyn commented 8 years ago

Mám to stále v neprečítaných aby som na to nezabudol. Chcel som to dnes pozriet, ale nevyšlo. Ak nevyriešime na stretku, tak sa ti na to zajtra večer pozriem.

Burgyn commented 8 years ago

Neoverená myšlienka, ktorá mi napadla v noci: Keby si v prvom kroku vymazal role, ktoré už užívateľ nemá k dispozícií. A následne by si len updatol užívateľa. Ten update by ti mal zabezpečiť to, že sa nové role pridajú. Bohužiaľ v EF Core 1 ešte nie je podporená relácia many-to-many bez spojovacej triedy a preto si myslím, že ten výmaz musíš robiť ručne. V EF 6.xx by ti stačilo zavolať update nad používateľom a všetko by sa zabezpečilo samé.

Ak toto nezaberie, tak problém môže byť ešte v mapovaní pomocou AutoMapper. Volaním User editedUser = AutoMapper.Mapper.Map(userVm, oldUser); spravíš to, že automapper premapuje vlastnosti z userVm do inštancie oldUser. Ak tá inštancia oldUser nemá načítaný zoznam rolí (a podľa toho, že tam nevidím žiadne Included pri načítavaní, asi nemá), tak automapper spraví to, že po vytvára nové inštancie triedy UserRole a pridá ich používateľovi. Keďže ef tieto inštancie netrackoval, tak ich považuje za nové a snaží sa ich pridať.

Sú to ale len dohady z tohto kúsku kódu, ktorý som videl. Chcelo by sa na to pozrieť hlbšie.

P.s.: Keďže sa ti tam duplikovali záznamy, asi by bolo fajn tam spraviť na to unikátny index. Nech sa o tom dozvieme, keby sa opäť niečo také stalo.

Mňa podobný problém asi čaká s vybavením miestností, takže ak to vyriešiš napíš. :-)

soy-grep commented 8 years ago

Kukal som doma tie problémy s editovaním používateľa. Asi by sme sa mali dohodnúť na princípe, ako budú fungovať Repository v prípade komplexnejších grafov entít. Ono totiž pridávanie a editácia Entít obsahujúcich kolekcie iných entít vie byť riadne tricky... A nebolo by dobré, ak by sa nám do kódu kontrolerov dostali "finty" špecifické pre Entity Framework...

Inak je to tak ako napísal Miňo - keď v kontroleri Userov v metóde Put vytiahneme inštanciu užívateľa oldUser, táto inštancia začne byť sledovaná ChangeTrackerom, momentálne je jej stav Unchanged. Keď zavoláme AutoMapper.Mapper.Map(userVm, oldUser) aj s naplnenou kolekciou UserRoles, všetky inštancie UserRole sa dostanú do ChangeTrackera v stave Added – bez ohľadu na to, či reálne v databáze takýto záznam jestvuje, alebo nie... a to je problém. Je to tým, že kolekcia UserRoles v trackovanej inštancii užívateľa je ostro sledovaná EF a akákoľvek jej zmena je premietnutá do ChangeTrackera...

V starších verziách EF som si na kontexte vypínal ProxyCreation... Ale v EF 7 akosi neviem tieto voľby nájsť :). A možno by to nepomohlo... neviem...

Ale vo všeobecnosti - Páčilo by sa mi, ak by UserRepository bolo zodpovedné aj za persistenciu UserRoles – pristupovali by sme k tomu ako ku Aggregate Root Repository. Akonáhle by sme zavolali na UserRepository metódu Edit, toto repository rozhodne aj o uložení/zmazaní UserRoles – Explicitne ponastavuje entitám UserRole v ChangeTrackeri stavy Modified/Added/Deleted... A to všetko by teda bolo v jednom UnitOfWork. Tzn. že pri UserRepository by sme Edit prepísali, bázový Edit nám tu už nestačí.

Ak by sme chceli editovať užívateľa bez vplyvu na jeho UserRoles, buď pošleme do metódy Edit inštanciu User s kolekciou UserRoles == null, alebo rozdelíme užívateľov Edit na dve metódy – Edit a EditWithRoles. Toto by sme museli ošetriť v Edit.

To znamená, že by sme tieto problémy špecifické pre konkrétny ORMapper ukryli pred vonkajším kódom – pred Kontrolermi.

Inak na túto tému je fajn kurz na Pluralsighte Entity Framework in the Enterprise Hlavne sekcia Understanding how EF handles disconnected graphs. Aj keď je to ku starému EF, myslím že správanie sa v EF7 výrazne nezmenilo.

V každom prípade je to téma na diskusiu :).

soy-grep commented 8 years ago

Moje myšlienky... možno zbytočne filozofujem, ale...

Z pohľadu ORM je "prirodzené" fungovanie editácie o tom, že Entita sa načíta z databázy, čiže sa urobí nejaký DbSet.Get(), táto inštancia začne byť trackovaná ChangeTrackerom. Kód si túto pripojenú inštanciu Entity voľne modifikuje a keď je spokojný, zavolá na DbContext.SaveChanges() - zmeny sa uložia. Preto niektoré príklady Repository patternu nad ORM nemajú metódu Edit... držia sa scenára kde je ChangeTracker zúčastnený na editácii od začiatku - modifikujeme vždy pripojené dáta. Akurát že Edit na Repository, ktoré abstrahuje dátové úložisko, jednoducho patrí. Hlavne, ak ho kŕmime aj odpojenými dátami z requestov, ktoré nám presýpa AutoMapper.

U nás je situácia taká, že keď si vypýtam cez niektorý Get inštanciu Entity a nedajbože ju začnem v kóde modifikovať, jej zmeny sa pri najbližšom Save do databázy uložia(!). A to aj keď sme oficiálne Edit na Repository nezavolali :). Budeme teda Edit brať ako metódu určenú práve pre zaradenie entity do ChangeTrackera?...

Tak ma napadá - nebolo by teda dobré, ak by sme v metódach Get vracali vždy odpojené entity? Vo väčšine prípadov aj tak budeme do Repository posielať nové inštancie z AutoMappera, ale práve v tomto prípade tomu tak nie je a podľa mňa to komplikuje situáciu.

:) najkrajšie na tom je, že ideálne riešenie zrejme neexistuje...

Burgyn commented 8 years ago

@majco333

Taktiež som sa detailnejšie pozrel na spomínanú editáciu používateľa. Jednoznačne súhlasím s @DedrickX, že špecifiká Entity Frameworku by mali byť skryté v repository a nie mimo neho. Veď práve preto využívame Repository pattern.

S tým AutoMapperom je to ešte trošku komplikovanejšie. Už spomínané preťaženie AutoMapper.Mapper.Map(userVm, oldUser) síce nevytvorí novú inštanciu Usera, ale iba presype hodnoty do inštancie oldUser. Problém je v tom, že ViewModel UserRoleViewModelneobsahuje Id ani UserId. Preto nie je možné AutoMapper nakonfigurovať tak aby v prípade, že oldUser obsahuje kolekciu UserRoles tak ju vrátil priamo a nevytváral nové inštancie. To, že UserRoleViewModel obsahuje iba RoleId je za mňa správne. Nemá zmysel na klienta posielať spomínané idčka. Takže nám ostáva jediná možnosť a to pri editácií usera správne ponastavovať statusy jednotlivým UserRole entitám. (Tak ako spomínal @DedrickX)

Chcel som si vytvoriť vlastnú vetvu kópiou tvojej (aj sa mi to podarilo) a tam spraviť zmeny ako som to myslel. Bohužiaľ nechápem prečo, commity dávalo priamo do tvojej vetvy :-( (commits 79a85c3 / d36179a )

Skúsim popísať čo som tam porobil. V prvom rade som upravil mapovanie UserViewModel na User pre automapper:

 this.CreateMap<User, UserViewModel>().ReverseMap().
                AfterMap((m, vm) =>
                {
                    foreach (var role in vm.UserRoles)
                    {
                        role.UserId = m.Id;
                    }
                });

Aby už pri spätnom mapovaní som dostal UserRole aj s idčkom usera a nemusel to robiť v repository.

Následne som upravil metódu Put v UserController-y tak aby neobsahovala veci okolo UserRoles. Takže tam ostalo len čisté _userRepository.Edit. Samozrejme overovačky na začiatku ;-)

 IActionResult result;
 User editedUser = AutoMapper.Mapper.Map(userVm, oldUser);

 result = SaveData(() =>
 {
     _userRepository.Edit(editedUser);
 });

 return result;

No a nakoniec overridnutá metóda Edit v UserRepository

var userRoles = item.UserRoles;

 item.UserRoles = null;
 base.Edit(item);

 if (userRoles != null)
 {
     var oldRoles = _dbContext.Set<UserRole>().Where(p => p.UserId == item.Id);

     foreach (var role in userRoles.Where(p => !HasUserRole(item.Id, p.RoleId)))
     {
         _dbContext.Entry(role).State = EntityState.Added;
     }

     foreach (var role in oldRoles.Where((r) => !userRoles.Any(p => (p.RoleId == r.RoleId))))
     {
         _dbContext.Entry(role).State = EntityState.Deleted;
     } 
 }

Kde si odložím kolekciu UserRoles, zavolám Edit z predka čisto len s userom (UserRoles som nastavil na null). Následne si zistím, ktoré role sú pridané a vymazané a nastavím im správny State. Určite sa to dá napísať aj ináč, ale princíp som snáď vystihol.

Chcem sa ešte prikloniť k názoru, že by UserRepository bol vnímaný ako Aggregate Root Repository. Aby sme redukovali množstvo repository. Aggregate Root Repository v jednoduchosti znamená, že pokiaľ nejaká entita samostatne nemá opodstatnenie. Že je vždy ako nestead inej entity, tak pre ňu neexistuje repository. Ale edituje sa spolu s rootom.

Burgyn commented 8 years ago

@DedrickX Súhlasím, že by sme sa mali dohodnúť na tom ako budeme k samotnej editácií pristupovať.

Poopravil by som Ťa v tom, že cez AutoMapper dávame vždy nové inštancie, pretože preťaženie spomenuté vyššie je práve o tom, aby naplnil dáta do existujúcej inštancie. Ale aj tak sa mi osobne Tvoj nápad s odpojenými entitami páči a prikláňam sa k nemu.

Nechceme na toto založiť samostatné issue? Ak sa na niečom dohodneme tak môžme spraviť issue, kde sa to zapracuje. (Teda zrefaktoruje existujúci kód podľa dohodnutých pravidiel)

Burgyn commented 8 years ago

@majco333 Tak ešte raz k tej editácií a AutoMapper-u. Neviem ako to je možné, ale u mňa s vybavením izby riešim v podstate to isté a nemusel som robiť tie záležitosti okolo nastavovania stavov. Nie úplne tomu rozumiem, pretože som sa všetky veci čo som urobil u seba snažil spraviť aj u teba, ale bez výsledku. Jediný rozdiel vidím v tom, že ja ešte stále nemám unikátny index nad kombináciou EquipmentId a RoomId. Môžeš skúsiť moju vetvu MinoEquipment. Možno na niečo prídeš.

majco333 commented 8 years ago

Diki chalani, skusim... Tiez som za to, na com ste sa dohodli.