Met behulp van LINQ to facebook profiel kaart te brengen met mijn gebruiker info

stemmen
3

Na het lezen van een boek over LINQ Ik ben het denken over het herschrijven van een mapper klasse die ik heb geschreven in C # om LINQ te gebruiken. Ik ben benieuwd of iemand me een hand kan geven. Let op: het is een beetje verwarrend, maar de gebruiker object is de lokale gebruiker en gebruiker (kleine letters) is het object gegenereerd uit de 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;
    }
}

Mijn eerste twee pogingen hit muren

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

poging 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);
}
De vraag is gesteld op 02/03/2009 om 20:32
bron van user
In andere talen...                            


2 antwoorden

stemmen
5

Ik zou geneigd zijn om zoiets als dit in plaats schrijven:

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

Toch zou ik niet beweren dat was een grote verbetering ten opzichte van wat je hebt (tenzij de gebruiker of facebook gebruiker collecties zijn zeer groot, in dat geval kunt u de wind met een merkbaar verschil in prestaties.)

(Ik zou adviseren tegen het gebruik Selectals een foreachlus tot een werkelijke muteren actie uit te voeren op een element van een set, de manier waarop je in je refactoring pogingen gedaan. U kunt dit doen, maar mensen zullen verrast zijn door de code, en je zult moeten lazy evaluatie in gedachten de hele tijd te houden.)

antwoordde op 28/03/2009 om 22:59
bron van user

stemmen
9

Oké, het is niet precies duidelijk wat IMapperin zich heeft, maar ik zou een paar dingen, waarvan sommige misschien niet haalbaar te wijten zijn aan andere beperkingen te stellen. Ik heb dit uitgeschreven min of meer zoals ik heb nagedacht over het - ik denk dat het helpt om de gedachtengang in actie te zien, want dat zal het gemakkelijker maken voor u om hetzelfde te doen de volgende keer. (Ervan uitgaande dat je van mijn oplossingen, natuurlijk :)

LINQ is van nature functioneel in stijl. Dat betekent dat idealiter queries mag niet bijwerkingen hebben. Bijvoorbeeld, zou ik verwachten dat een methode met een handtekening van:

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

een nieuwe reeks gebruikersobjecten keren met extra informatie, in plaats van het muteren van de huidige gebruiker. De enige informatie die u momenteel het toevoegen is de avatar, dus ik zou een methode toe te voegen in Userde trant van:

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

Je zou zelfs willen maken Uservolledig onveranderlijk - er zijn verschillende strategieën rond dat, zoals de bouwer patroon. Vraag me als je wilt meer informatie. Hoe dan ook, het belangrijkste is dat we een nieuwe gebruiker die een kopie is van de oude hebt gemaakt, maar met de opgegeven avatar.

Eerste poging: inner join

Nu terug naar uw mapper ... je hebt momenteel nog drie publieke methoden, maar mijn gok is dat alleen de eerste die moet openbaar zijn, en dat de rest van de API niet echt nodig om de Facebook-gebruikers bloot te leggen. Het lijkt erop dat uw GetFacebookUsersmethode is in principe goed, hoewel ik zou waarschijnlijk line-up van de vraag in termen van witruimte.

Dus, gegeven een reeks van lokale gebruikers en een verzameling van de Facebook-gebruikers, zijn we vertrokken het doen van de werkelijke mapping bit. Een straight "join" clausule is problematisch, omdat het niet de lokale gebruikers die niet een bijpassende Facebook gebruiker hebben zal opleveren. In plaats daarvan moeten we een of andere manier van het behandelen van een niet-Facebook-gebruiker alsof ze een Facebook-gebruiker zonder een avatar. In wezen is dit de null object patroon.

We kunnen dat doen door te komen met een Facebook-gebruiker die een null uid heeft (ervan uitgaande dat het object model maakt het mogelijk dat):

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

Maar we willen eigenlijk een opeenvolging van deze gebruikers, want dat is wat Enumerable.Concatgebruik maakt van:

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

Nu kunnen we alleen maar "add" deze dummy toegang tot onze echte, en doe een normale inner join. Merk op dat dit gaat ervan uit dat de lookup van Facebook-gebruikers altijd een gebruiker voor een "echte" Facebook UID te vinden. Als dat niet het geval is, zouden we nodig hebben om dit te herzien en niet gebruik maken van een inner join.

We nemen de "nul" gebruiker aan het einde, doe dan het te sluiten en project met behulp van 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);
}

Dus de volledige klas zou zijn:

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

Een paar punten hier:

  • Zoals eerder opgemerkt, de inner join wordt problematisch wanneer een gebruiker Facebook UID niet zou kunnen worden gehaald als een geldige gebruiker.
  • Op dezelfde manier krijgen we problemen als we dubbele Facebook-gebruikers - elke lokale gebruiker zou uiteindelijk coming out twee keer!
  • Dit vervangt (verwijdert) de avatar voor niet-Facebook-gebruikers.

Een tweede benadering: groep aan te sluiten

Laten we eens kijken of we deze punten kunnen pakken. Ik ga ervan uit dat als we hebben opgehaald meerdere Facebook-gebruikers voor een enkele Facebook UID, dan maakt het niet uit wie van hen we pak de avatar uit - ze moeten hetzelfde zijn.

Wat we nodig hebben is een groep aan te sluiten, zodat voor elke lokale gebruiker een reeks van de aanpassing van de Facebook-gebruikers krijgen we. Vervolgens zullen we gebruiken DefaultIfEmptyom het leven gemakkelijker te maken.

We kunnen houden WithAvatarzoals het was - maar deze keer gaan we alleen maar om het te bellen als we een Facebook-gebruiker aan de avatar uit te pakken hebt. Een groep mee in C # query expressies wordt voorgesteld door join ... into. Deze vraag is redelijk lang, maar het is niet te eng, eerlijk!

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

Hier is de query-expressie weer, maar met opmerkingen:

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

De niet-LINQ oplossing

Een andere optie is om alleen in zeer geringe mate gebruik maken van LINQ. Bijvoorbeeld:

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

Dit maakt gebruik van een repeater blok in plaats van een LINQ query-expressie. ToDictionaryzal een uitzondering te gooien als het dezelfde toets twee keer ontvangt - een optie om te werken rond dit is om te veranderen GetFacebookUsersom ervoor te zorgen dat het kijkt alleen voor verschillende ID's:

    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
    }

Dat veronderstelt de webservice werkt naar behoren, natuurlijk - maar als het niet gebeurt, wilt u waarschijnlijk een uitzondering toch te gooien :)

Conclusie

Maak uw keuze uit de drie. De groep aan te sluiten is waarschijnlijk het moeilijkst te begrijpen, maar gedraagt zich best. De iterator blok oplossing is misschien wel de eenvoudigste en moeten gedragen goed met de GetFacebookUserswijziging.

Het maken van Useronveranderlijke zou vrijwel zeker een positieve stap wel.

Een leuke bijproduct van al deze oplossingen is dat de gebruikers uit te komen in dezelfde volgorde waarin ze in gingen. Dat is misschien ook niet belangrijk voor je zijn, maar het kan een mooie woning.

Hoop dat dit helpt - het is een interessante vraag :)

EDIT: Is mutatie de weg te gaan?

Na het zien in je reactie dat de lokale gebruiker type is eigenlijk een soort entiteit van de entiteit kader is het misschien niet gepast om deze gang van zaken te nemen. Waardoor het onveranderlijk is vrij veel uit van de vraag, en ik vermoed dat de meeste toepassingen van het type zal verwachten mutatie.

Als dat het geval is, kan het de moeite waard het veranderen van de interface te maken dat duidelijker. In plaats van terug te keren een IEnumerable<User>(wat impliceert - tot op zekere hoogte - projectie) je zou willen zowel de ondertekening en de naam te wijzigen, zodat u met iets als dit:

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

Nogmaals, dit is niet een bijzonder "LINQ-y"-oplossing (in de belangrijkste operatie) niet meer - maar dat is redelijk, als je niet echt "bevraging"; je bent "vernieuwing".

antwoordde op 01/04/2009 om 20:28
bron van user

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