Mit Linq zur Karte Facebook-Profils mit meiner Benutzerinfo

stimmen
3

Nach der Lektüre ich denke, ein Buch über LINQ über eine Mapper-Klasse neu zu schreiben, die ich in c # geschrieben LINQ zu verwenden. Ich frage mich, wenn jemand mir eine Hand geben kann. Hinweis: es ist ein wenig verwirrend, aber das User-Objekt ist die lokalen Benutzer und Benutzer (klein geschrieben) ist das Objekt aus dem Facebook XSD generiert.

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

Meine ersten beiden Versuche schlagen Wände

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

Versuch 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);
}
Veröffentlicht am 02/03/2009 um 20:32
quelle vom benutzer
In anderen Sprachen...                            


2 antworten

stimmen
5

Ich würde geneigt sein, so etwas wie diese, anstatt zu schreiben:

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

Allerdings würde ich nicht behaupten, dass eine große Verbesserung gegenüber war, was du hast (es sei denn, der Benutzer oder Facebook-Benutzer Sammlungen sind sehr groß, in welchem ​​Fall Sie könnte mit einem spürbaren Unterschied in der Leistung aufzuwickeln.)

(Ich würde empfehlen , gegen die Verwendung von Selectwie eine foreachSchleife eine tatsächliche Mutieren Aktion auf ein Element eines Satzes auszuführen, wie Sie in Ihrem Refactoring versucht haben. Sie können es tun, aber die Leute von Ihrem Code überrascht sein, und Sie werden muß lazy evaluation im Auge behalten , die ganze Zeit.)

Beantwortet am 28/03/2009 um 22:59
quelle vom benutzer

stimmen
9

Okay, es ist nicht klar , genau das, was IMapperin ihm hat, aber ich würde ein paar Dinge vorschlagen, von denen einige aufgrund anderer Einschränkungen nicht durchführbar sein. Ich habe das ziemlich viel geschrieben, wie ich darüber nachgedacht habe - ich glaube , es hilft , den Gedankengang in Aktion zu sehen, wie es einfacher zu machen , wird für Sie die gleiche Sache beim nächsten Mal zu tun. (Vorausgesetzt , dass Sie meine Lösungen mögen, natürlich :)

LINQ ist von Natur aus funktionalem Stil. Das bedeutet, dass im Idealfall Abfrage sollte keine Nebenwirkungen hat. Zum Beispiel würde ich eine Methode mit einer Signatur von erwarten:

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

eine neue Folge von Benutzerobjekten mit zusätzlichen Informationen zurückzukehren, anstatt die bestehenden Benutzer mutiert. Die einzige Information , Sie gerade anhängen ist der Avatar, also würde ich eine Methode in hinzufügen Userentlang der Linien von:

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

Sie könnten sogar machen wollen Uservöllig unveränderlich - gibt es verschiedene Strategien um , dass, wie die Erbauer. Fragen Sie mich , wenn Sie weitere Informationen wünschen. Wie auch immer, die Hauptsache ist , dass wir einen neuen Benutzer erstellt haben , die eine Kopie der alten, aber mit dem angegebenen Avatar.

Erster Versuch: INNER JOIN

Nun zurück zu Ihrem Mapper ... Sie haben derzeit drei bekommen öffentliche Methoden , aber meine Vermutung ist , dass nur die erste zu sein , die Öffentlichkeit braucht, und dass der Rest der API muss nicht tatsächlich die Facebook - Nutzer verfügbar machen. Es sieht aus wie Ihre GetFacebookUsersMethode grundsätzlich in Ordnung, obwohl ich wahrscheinlich die Abfrage in Bezug auf die Leerzeichen in einer Reihe aufstellen würde.

Also, da eine Folge von lokalen Benutzern und eine Sammlung von Facebook-Nutzer, wir links den eigentlichen Mapping Bit zu tun. Eine gerade „Join“ Klausel ist problematisch, weil es nicht die lokalen Benutzer ergeben, die keine passenden Facebook-Nutzer haben. Stattdessen brauchen wir eine Möglichkeit, einen Nicht-Facebook-Nutzer zu behandeln, als ob sie ein Facebook-Nutzer ohne einen Avatar waren. Im Wesentlichen ist dies das Null-Objekt-Muster.

Wir können das tun, indem sie mit einem Facebook-Benutzer, der eine Null-uid hat kommen (das Objektmodell unter der Annahme erlaubt, dass):

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

Wir wollen aber eigentlich eine Folge dieser Nutzer, denn das ist , was Enumerable.Concatverwendet:

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

Jetzt können wir einfach „add“ diese Dummy - Eintrag in unseren realen und tun eine normale innere Verknüpfung. Beachten Sie, dass dies geht davon aus, dass die Suche von Facebook - Nutzer wird immer einen Benutzer für jede „echte“ Facebook UID finden. Wenn das nicht der Fall ist, müssten wir dies nochmals zu besuchen und nicht verwenden eine innere Verknüpfung.

Wir schließen die „Null“ Benutzer am Ende, hat dann die Verbindung und Projekt mit 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);
}

So ist die volle Klasse wä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
    }
}

Ein paar Punkte hier:

  • Wie bereits erwähnt, kommt die innere problematisch wird, wenn ein Facebook-UID des Benutzers nicht als gültigen Benutzer abgerufen werden kann.
  • Ebenso bekommen wir Probleme, wenn wir doppelte Facebook-Nutzer haben - jeder lokale Benutzer würde zweimal herauskommen am Ende!
  • Dies ersetzt (entfernt), um den Avatar für Nicht-Facebook-Nutzer.

Ein zweiter Ansatz: Gruppe beitreten

Mal sehen , ob wir diese Punkte ansprechen können. Ich gehe davon aus, dass , wenn wir geholt haben mehrere Facebook - Nutzer für eine einzelne Facebook UID, dann ist es nicht, wer von ihnen egal , greifen wir den Avatar aus - sie sollten gleich sein.

Was wir brauchen , ist eine Gruppe beitreten, so dass für jeden lokalen Benutzer wir eine Sequenz von passenden Facebook - Nutzer erhalten. Verwenden wir dann DefaultIfEmptydas Leben leichter zu machen.

Wir können halten , WithAvatarwie es vorher war - aber diesmal wir es nur gehen zu nennen , wenn wir einen Facebook - Nutzer haben von dem Avatar zu greifen. Eine Gruppe beitreten in C # Abfrage - Ausdrücken dargestellt durch join ... into. Diese Abfrage ist ziemlich lang, aber es ist nicht zu gruselig, ehrlich!

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 ist der Abfrage-Ausdruck wieder, aber mit Kommentaren:

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

Die nicht-LINQ-Lösung

Eine weitere Option ist nur LINQ zu verwenden, sehr leicht. Beispielsweise:

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

Dieser verwendet einen Iteratorblock anstelle einer LINQ - Abfrage - Ausdruck. ToDictionarywird eine Ausnahme werfen , wenn er zweimal den gleichen Schlüssel erhält - eine Option , um dieses Werk zu ändern ist GetFacebookUserses sieht nur für eindeutigen IDs , um sicherzustellen:

    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
    }

Das übernimmt der Web-Service funktioniert in geeigneter Weise, natürlich - aber wenn es nicht tut, wollen Sie wahrscheinlich eine Ausnahme sowieso werfen :)

Fazit

Treffen Sie Ihre Wahl aus den dreien. Die Gruppe beizutreten , ist wahrscheinlich am schwersten zu verstehen, sondern verhält sich am besten. Die Iteratorblock Lösung ist vielleicht die einfachste und sollte mit der Ordnung verhalten GetFacebookUsersModifikation.

Etwas herstellen , Userobwohl unveränderlich wäre mit ziemlicher Sicherheit ein positiver Schritt.

Ein schönes Nebenprodukt all diese Lösungen besteht darin, dass der Anwender kommt in der gleichen Reihenfolge, in sie gingen. Das mag für Sie nicht wichtig sein, aber es kann ein schönes Hotel sein.

Hoffe, das hilft - es war eine interessante Frage :)

EDIT: Ist Mutation der Weg zu gehen?

Nachdem in Ihren Kommentaren zu sehen , dass der lokale Benutzertyp tatsächlich ein Entitätstyp aus dem Entity Framework ist, es kann nicht sinnvoll sein , diese Vorgehensweise zu nehmen. Es unveränderlich zu machen ist ziemlich außer Frage, und ich vermute , dass die meisten Anwendungen des Typs erwarten Mutation.

Wenn das der Fall ist, kann es sich lohnen , wenn Sie Ihre Schnittstelle dass klarer zu machen. Statt der Rückkehr ein IEnumerable<User>(was bedeutet , - bis zu einem gewissen Grad - Projektion) Sie sowohl die Unterschrift und den Namen ändern möchten, können Sie mit so etwas wie dieses zu verlassen:

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

Auch dies ist nicht besonders „LINQ-y“ Lösung (im Hauptbetrieb) nicht mehr - aber das ist sinnvoll, da man nicht wirklich „anfragende“ sind; Sie „Aktualisierung“.

Beantwortet am 01/04/2009 um 20:28
quelle vom benutzer

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