Bruke Linq å kartlegge facebook profil med min brukerinfo

stemmer
3

Etter å ha lest en bok om LINQ tenker jeg omskrivninger en mapper klasse som jeg skrev i C # å bruke LINQ. Jeg lurer på om noen kan gi meg en hånd. Merk: det er litt forvirrende, men brukerobjektet er den lokale brukeren, og brukeren (små bokstaver) er objektet som genereres fra Facebook XSD.

Original Mapper

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

Mine to første forsøk treffe vegger

forsøk 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;
}

forsøk 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);
}
Publisert på 02/03/2009 klokken 19:32
kilden bruker
På andre språk...                            


2 svar

stemmer
5

Jeg ville være tilbøyelig til å skrive noe sånt som dette i stedet:

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

Men jeg ville ikke påstå at var en stor forbedring over hva du har (med mindre brukeren eller Facebook-bruker samlingene er svært store, og i så fall kan du ende opp med en merkbar ytelse forskjell.)

(Jeg vil anbefale mot å bruke Selectsom en foreachsløyfe for å utføre en faktisk mutere handling på et element av et sett, slik du gjorde i dine refactoring forsøk. Du kan gjøre det, men folk vil bli overrasket av koden din, og du vil må holde lat evaluering i tankene hele tiden.)

Svarte 28/03/2009 kl. 21:59
kilden bruker

stemmer
9

Ok, det er ikke klart nøyaktig hva IMapperhar i det, men jeg vil foreslå et par ting, noen som kanskje ikke er gjennomførbart på grunn av andre restriksjoner. Jeg har skrevet dette ut ganske mye som jeg har tenkt på det - jeg tror det hjelper å se tankerekken i aksjon, som det vil gjøre det lettere for deg å gjøre det samme neste gang. (Forutsatt at du liker mine løsninger, selvfølgelig :)

LINQ er iboende funksjonell stil. Det betyr at ideelt sett, spørsmål bør ikke ha bivirkninger. For eksempel, jeg forventer en metode med en signatur av:

public IEnumerable<User> MapFrom(IEnumerable<User> users)

å returnere en ny sekvens av brukerobjekter med ekstra informasjon, heller enn å mutere eksisterende brukere. Den eneste informasjonen du nå føye er avataren, så jeg vil legge til en metode i Userretning av:

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

Du kan selv ønsker å lage Userfullt uforanderlige - det finnes ulike strategier rundt dette, slik som byggmester mønster. Spør meg hvis du vil ha mer informasjon. Uansett, er det viktigste at vi har opprettet en ny bruker som er en kopi av den gamle, men med den angitte avatar.

Første forsøk: inner delta

Nå tilbake til mapper ... du har nå fått tre offentlige metoder, men min gjetning er at bare det første man trenger å være offentlig, og at resten av API faktisk ikke trenger å utsette Facebook-brukere. Det ser ut til at GetFacebookUsersmetoden er i utgangspunktet greit, selv om jeg ville trolig stille opp spørsmålet når det gjelder mellomrom.

Så, gitt en sekvens av lokale brukere og en samling av Facebook-brukere, vi forlot gjør selve kartleggingen bit. En rett "join" -klausulen er problematisk, fordi det ikke vil gi de lokale brukere som ikke har en matchende Facebook-bruker. I stedet må vi noen måte å behandle en ikke-Facebook-bruker som om de var en Facebook-bruker uten en avatar. I hovedsak er dette nullgjenstandsmønsteret.

Vi kan gjøre det ved å komme opp med en Facebook-bruker som har en null uid (forutsatt objektmodellen gjør det):

// Adjust for however the user should actually be constructed.
private static readonly FacebookUser NullFacebookUser = new FacebookUser(null);

Men vi faktisk ønsker en sekvens av disse brukerne, fordi det er det Enumerable.Concatbruker:

private static readonly IEnumerable<FacebookUser> NullFacebookUsers =
    Enumerable.Repeat(new FacebookUser(null), 1);

Nå kan vi bare "legge" dette dummy innreise til vår ekte en, og gjøre en normal indre delta. Merk at dette forutsetter at oppslag av Facebook-brukere vil alltid finne en bruker for noen "ekte" Facebook UID. Hvis det ikke er tilfelle, ville vi trenger å se dette, og ikke bruke en indre delta.

Vi inkludere "null" bruker på slutten, gjør deretter delta og prosjekt med 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);
}

Så hele klassen vil være:

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

Noen punkter her:

  • Som nevnt før, det indre delta blir problematisk hvis en brukers Facebook UID kanskje ikke hentet som en gyldig bruker.
  • Tilsvarende får vi problemer hvis vi har like Facebook-brukere - hver lokal bruker ville ende opp som kommer ut to ganger!
  • Dette erstatter (fjerner) avataren for ikke-Facebook-brukere.

En annen fremgangsmåte: gruppe bli

La oss se om vi kan løse disse punktene. Jeg vil anta at hvis vi har hentet flere Facebook-brukere for en enkelt Facebook UID, så det spiller ingen rolle hvilken av dem vi ta tak i avatar fra - de bør være den samme.

Det vi trenger er en gruppe delta, slik at det for hver lokal bruker får vi en sekvens av matchende Facebook-brukere. Vi vil da bruke DefaultIfEmptyfor å gjøre livet enklere.

Vi kan holde WithAvatarsom det var før - men denne gangen vi bare kommer til å kalle det hvis vi har en Facebook-bruker å ta tak i avatar fra. En gruppe delta i C # spørreuttrykk er representert med join ... into. Dette søket er rimelig lang, men det er ikke så skummelt, ærlig!

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);
}

Her er spørsmålet uttrykket igjen, men med kommentarer:

// "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);

Den ikke-LINQ løsning

Et annet alternativ er å bare bruke LINQ svært svakt. For eksempel:

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

Denne benytter en iteratorblokk i stedet for en spørring LINQ uttrykk. ToDictionaryvil kaste et unntak hvis den mottar den samme nøkkelen to ganger - en mulighet til å omgå dette på er å endre GetFacebookUsersfor å sørge for at det bare ser for forskjellige IDer:

    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
    }

Det foruts nettet tjenesten fungerer på riktig måte, selvfølgelig - men hvis det ikke gjør det, har du sannsynligvis ønsker å kaste et unntak likevel :)

Konklusjon

Ta plukke ut av de tre. Gruppen delta er sannsynligvis vanskeligst å forstå, men oppfører beste. Den iteratorblokk Løsningen er muligens det enkleste, og bør oppføre seg greit med GetFacebookUsersmodifikasjon.

Making Useruforanderlige ville nesten helt sikkert være et positivt skritt skjønt.

En fin biprodukt av alle disse løsningene er at brukerne kommer ut i samme rekkefølge som de gikk i. Det kan vel ikke være viktig for deg, men det kan være en fin egenskap.

Håper dette hjelper - det har vært et interessant spørsmål :)

EDIT: Er mutasjon veien å gå?

Etter å ha sett i dine kommentarer at det lokale bruker typen er faktisk en enhet type fra Entity Framework, det kan ikke være riktig å ta dette kurset av handlingen. Gjør det uforanderlige er ganske mye ut av spørsmålet, og jeg mistenker at de fleste anvendelser av typen vil forvente mutasjon.

Hvis det er tilfelle, kan det være verdt å endre grensesnittet for å gjøre det klarere. I stedet for å returnere en IEnumerable<User>(som tilsier - til en viss grad - projeksjon) kan det være lurt å endre både signatur og navnet, og du har noe sånt som dette:

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

Igjen, dette er ikke en spesielt "LINQ-y" løsning (i hoved drift) noe mer - men det er rimelig, som du egentlig ikke "spørring"; du "oppdatering".

Svarte 01/04/2009 kl. 19:28
kilden bruker

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