Bine, nu este clar exact ceea ce IMapperare în ea, dar mi - ar sugera câteva lucruri, dintre care unele nu pot fi fezabile din cauza altor restricții. Am scris asta destul de mult ca m - am gândit la asta - cred că ajută să vedem trenul de gândire în acțiune, deoarece acest lucru va face mai ușor pentru tine de a face același lucru data viitoare. (Presupunând că vă place soluțiile mele, desigur :)
LINQ este în mod inerent funcțional în stil. Asta înseamnă că, în mod ideal, interogările nu ar trebui să aibă efecte secundare. De exemplu, mi-ar aștepta la o metodă cu o semnătură de:
public IEnumerable<User> MapFrom(IEnumerable<User> users)
pentru a returna o nouă secvență de obiecte de utilizator cu informații suplimentare, mai degrabă decât mutația utilizatorilor existenți. Singurele informații pe care sunteți în prezent este adăugarea avatarul, așa că aș adăuga o metodă în Userlungul liniilor de:
public User WithAvatar(Image avatar)
{
// Whatever you need to create a clone of this user
User clone = new User(this.Name, this.Age, etc);
clone.FacebookAvatar = avatar;
return clone;
}
S-ar putea dori chiar să facă pe Userdeplin imuabile - există diverse strategii în jurul valorii de faptul că, cum ar fi modelul de constructor. Întreabă - mă dacă vrei mai multe detalii. Oricum, principalul lucru este că am creat un nou utilizator , care este o copie a celei vechi, dar cu avatarul specificat.
Prima încercare: se alăture interior
Acum , înapoi la cartograf ta ... ai în prezent trei publice metode , dar mea presupunere este că numai prima trebuie să fie public, iar restul API nu are nevoie de fapt , pentru a expune utilizatorii Facebook. Se pare că dumneavoastră GetFacebookUsersmetodă este , în principiu în regulă, deși aș linie , probabil, interogarea în termeni de spații libere.
Deci, având în vedere o secvență de utilizatori locali și o colecție de utilizatori Facebook, vom face la stânga bit real de cartografiere. Un „se alăture“ clauză de drept este problematică, deoarece nu va da utilizatorilor locali care nu au un utilizator de potrivire Facebook. În schimb, avem nevoie de un mod de a trata un utilizator non-Facebook ca și cum ar fi un utilizator de Facebook, fără un avatar. În esență, aceasta este modelul de obiect nul.
Putem face acest lucru venind cu un utilizator Facebook care are uid-nul (presupunând că modelul de obiect permite acest lucru):
// Adjust for however the user should actually be constructed.
private static readonly FacebookUser NullFacebookUser = new FacebookUser(null);
Cu toate acestea, dorim de fapt o secvență de acești utilizatori, pentru că asta e ceea ce Enumerable.Concatfolosește:
private static readonly IEnumerable<FacebookUser> NullFacebookUsers =
Enumerable.Repeat(new FacebookUser(null), 1);
Acum putem pur și simplu „adăuga“ această intrare fals la un nostru real și alăturați unui astfel de interior normal. Rețineți că acest lucru presupune că cautartea de utilizatori Facebook vor găsi întotdeauna un utilizator pentru orice „reală“ Facebook UID. În cazul în care nu este cazul, ne - ar trebui să revizuiască acest lucru și să nu utilizați alătura unui interior.
Includem utilizatorul „nul“ , la final, apoi se join și de proiect folosind WithAvatar:
public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
var facebookUsers = GetFacebookUsers(users).Concat(NullFacebookUsers);
return from user in users
join facebookUser in facebookUsers on
user.FacebookUid equals facebookUser.uid
select user.WithAvatar(facebookUser.Avatar);
}
Deci, clasa completă ar fi:
public sealed class FacebookMapper : IMapper
{
private static readonly IEnumerable<FacebookUser> NullFacebookUsers =
Enumerable.Repeat(new FacebookUser(null), 1);
public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
var facebookUsers = GetFacebookUsers(users).Concat(NullFacebookUsers);
return from user in users
join facebookUser in facebookUsers on
user.FacebookUid equals facebookUser.uid
select user.WithAvatar(facebookUser.pic_square);
}
private Facebook.user[] GetFacebookUsers(IEnumerable<User> users)
{
var uids = (from u in users
where u.FacebookUid != null
select u.FacebookUid.Value).ToList();
// return facebook users for uids using WCF
}
}
Câteva puncte aici:
- După cum sa menționat mai înainte, interior se alăture devine problematică în cazul în care Facebook UID unui utilizator ar putea să nu fi preluat ca un utilizator valid.
- La fel obținem probleme dacă avem utilizatori duplicat Facebook - fiecare utilizator local va încheia iese de două ori!
- Acesta înlocuiește (Îndepărtează) avatarul pentru utilizatorii non-Facebook.
O a doua abordare: grup se alăture
Să vedem dacă putem aborda aceste puncte. Voi presupune că , dacă ne - am adus de mai mulți utilizatori Facebook pentru un singur Facebook UID, atunci nu contează care dintre ele le apuca avatarul de la - acestea ar trebui să fie la fel.
Ceea ce avem nevoie este un grup se alăture, astfel încât pentru fiecare utilizator local vom obține o secvență de potrivire utilizatori Facebook. Vom folosi apoi DefaultIfEmptypentru a face viața mai ușoară.
Putem păstra WithAvatarașa cum a fost înainte - dar de data aceasta vom doar o să - l sun dacă ne - am luat un utilizator Facebook pentru a apuca avatarul de la. Un grup se alăture în C # expresii de interogare este reprezentat de join ... into. Această interogare este destul de lung, dar nu este prea înfricoșător, cinstit!
public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
var facebookUsers = GetFacebookUsers(users);
return from user in users
join facebookUser in facebookUsers on
user.FacebookUid equals facebookUser.uid
into matchingUsers
let firstMatch = matchingUsers.DefaultIfEmpty().First()
select firstMatch == null ? user : user.WithAvatar(firstMatch.pic_square);
}
Iată expresia de interogare din nou, dar cu comentarii:
// "Source" sequence is just our local users
from user in users
// Perform a group join - the "matchingUsers" range variable will
// now be a sequence of FacebookUsers with the right UID. This could be empty.
join facebookUser in facebookUsers on
user.FacebookUid equals facebookUser.uid
into matchingUsers
// Convert an empty sequence into a single null entry, and then take the first
// element - i.e. the first matching FacebookUser or null
let firstMatch = matchingUsers.DefaultIfEmpty().First()
// If we've not got a match, return the original user.
// Otherwise return a new copy with the appropriate avatar
select firstMatch == null ? user : user.WithAvatar(firstMatch.pic_square);
Non-LINQ soluției
O altă opțiune este de a utiliza doar LINQ foarte ușor. De exemplu:
public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
var facebookUsers = GetFacebookUsers(users);
var uidDictionary = facebookUsers.ToDictionary(fb => fb.uid);
foreach (var user in users)
{
FacebookUser fb;
if (uidDictionary.TryGetValue(user.FacebookUid, out fb)
{
yield return user.WithAvatar(fb.pic_square);
}
else
{
yield return user;
}
}
}
Acesta utilizează un bloc iterator în loc de o expresie de interogare LINQ. ToDictionaryva arunca o excepție în cazul în care primește aceeași cheie de două ori - o opțiune de a lucra în jurul valorii de acest lucru este de a schimba GetFacebookUserspentru a se asigura că arată doar pentru ID - uri distincte:
private Facebook.user[] GetFacebookUsers(IEnumerable<User> users)
{
var uids = (from u in users
where u.FacebookUid != null
select u.FacebookUid.Value).Distinct().ToList();
// return facebook users for uids using WCF
}
Aceasta presupune serviciul web funcționează corespunzător, desigur -, dar dacă nu, probabil că doriți să arunce o excepție oricum :)
Concluzie
Alegei din cele trei. Grupul este , probabil , cel mai greu se alăture pentru a înțelege, dar se comportă cel mai bine. Solutia bloc iterator este probabil cel mai simplu, și ar trebui să se comporte bine cu GetFacebookUsersmodificarea.
Efectuarea Userimuabil ar fi aproape sigur un pas pozitiv , deși.
Un frumos produs al tuturor acestor soluții este faptul că utilizatorii ies în aceeași ordine au intrat. Asta nu ar putea fi important pentru tine, dar poate fi o proprietate frumos.
Sper că acest lucru ajută - că a fost o întrebare interesantă :)
EDIT: Este mutatie mod de a merge?
După ce a văzut în comentariile dumneavoastră că tipul de utilizator local este de fapt un tip de entitate din cadrul entității, acesta poate să nu fie necesar să se ia acest curs de acțiune. Acest site este imuabil este destul de mult iese din discuție, și bănuiesc că cele mai multe utilizări ale tipului va aștepta mutație.
Dacă acesta este cazul, acesta poate fi în valoare de a schimba interfața dvs. pentru a face asta mai clar. În loc de a se întoarce o IEnumerable<User>(ceea ce implică - într -o anumită măsură - proiecție) pe care s - ar putea dori să se schimbe atât semnătura și numele, lăsându - vă cu ceva de genul:
public sealed class FacebookMerger : IUserMerger
{
public void MergeInformation(IEnumerable<User> users)
{
var facebookUsers = GetFacebookUsers(users);
var uidDictionary = facebookUsers.ToDictionary(fb => fb.uid);
foreach (var user in users)
{
FacebookUser fb;
if (uidDictionary.TryGetValue(user.FacebookUid, out fb)
{
user.Avatar = fb.pic_square;
}
}
}
private Facebook.user[] GetFacebookUsers(IEnumerable<User> users)
{
var uids = (from u in users
where u.FacebookUid != null
select u.FacebookUid.Value).Distinct().ToList();
// return facebook users for uids using WCF
}
}
Din nou, acest lucru nu este o soluție deosebit de „LINQ-y“ (în operațiunea principală) mai - dar asta e rezonabil, așa cum nu ești cu adevărat „interogari“; te „actualizarea“.