Utilizarea LINQ hartă profilul Facebook cu datele mele de utilizare

voturi
3

Dupa ce a citit o carte despre LINQ mă gândesc la re-scrierea unei clase cartograf pe care l-am scris în C # pentru a utiliza LINQ. Mă întrebam dacă cineva mă poate da o mână. Notă: sale un pic confuz, dar obiectul Utilizatorul este de utilizator local și utilizator (cu litere mici), este obiectul generat de Facebook XSD.

Mapper Original

public class FacebookMapper : IMapper
{
    public IEnumerable<User> MapFrom(IEnumerable<User> users)
    {
      var facebookUsers = GetFacebookUsers(users);
      return MergeUsers(users, facebookUsers);
    }

    public 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
    }

    public IEnumerable<User> MergeUsers(IEnumerable<User> users, Facebook.user[] facebookUsers)
    {
      foreach(var u in users)
      {
        var fbUser = facebookUsers.FirstOrDefault(f => f.uid == u.FacebookUid);
        if (fbUser != null)
          u.FacebookAvatar = fbUser.pic_sqare;
      }
      return users;
    }
}

Primele mele două încercări lovit pereți

Încercare 1

public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
  // didn't have a way to check if u.FacebookUid == null
  return from u in users
    join f in GetFacebookUsers(users) on u.FacebookUid equals f.uid
    select AppendAvatar(u, f);
}

public void AppendAvatar(User u, Facebook.user f)
{
  if (f == null)
    return u;
  u.FacebookAvatar = f.pic_square;
  return u;
}

Încercare 2

public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
  // had to get the user from the facebook service for each single user,
  // would rather use a single http request.
  return from u in users
    let f = GetFacebookUser(user.FacebookUid)
    select AppendAvatar(u, f);
}
Întrebat 02/03/2009 la 20:32
sursa de către utilizator
În alte limbi...                            


2 răspunsuri

voturi
5

Aș fi înclinat să scrie ceva de genul în loc:

public class FacebookMapper : IMapper
{
    public IEnumerable<User> MapFacebookAvatars(IEnumerable<User> users)
    {
        var usersByID =
            users.Where(u => u.FacebookUid.HasValue)
                 .ToDictionary(u => u.FacebookUid.Value);

        var facebookUsersByID =
            GetFacebookUsers(usersByID.Keys).ToDictionary(f => f.uid);

        foreach(var id in usersByID.Keys.Intersect(facebookUsersByID.Keys))
            usersByID[id].FacebookAvatar = facebookUsersByID[id].pic_sqare;

        return users;
    }

    public Facebook.user[] GetFacebookUsers(IEnumerable<int> uids)
    {
       // return facebook users for uids using WCF
    }
}

Cu toate acestea, nu aș pretinde că a fost o mare imbunatatire peste ceea ce ai (cu excepția cazului în colecțiile de utilizator de utilizator sau Facebook sunt foarte mari, caz în care s-ar putea eoliene cu o diferență de performanță notabilă.)

(Aș recomanda împotriva utilizării Selectca o foreachbuclă pentru a efectua o acțiune mutant reală pe un element al unui set, așa cum ai făcut în încercarea de refactoring. Poti face acest lucru, dar oamenii vor fi surprinși de codul și veți trebuie să țină evaluare leneș în minte tot timpul.)

Publicat 28/03/2009 la 22:59
sursa de către utilizator

voturi
9

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“.

Publicat 01/04/2009 la 20:28
sursa de către utilizator

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more