Utilizzando LINQ to map profilo facebook con il mio informazioni utente

voti
3

Dopo aver letto un libro su LINQ Sto pensando di ri-scrittura di una classe mapper che ho scritto in C # per usare LINQ. Mi chiedo se qualcuno mi può dare una mano. Nota: la sua un po 'di confusione, ma l'oggetto utente è l'utente locale e l'utente (in minuscolo) è l'oggetto generato dal Facebook XSD.

Mapper originale

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

I miei primi due tentativi di colpire le pareti

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

tentativo 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);
}
È pubblicato 02/03/2009 alle 20:32
fonte dall'utente
In altre lingue...                            


2 risposte

voti
5

Sarei propenso a scrivere qualcosa di simile a questo, invece:

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

Tuttavia, non vorrei affermare che è stato un grande miglioramento rispetto quello che hai (a meno che l'utente o Facebook collezioni utenti sono molto grandi, nel qual caso si potrebbe finire con una differenza di prestazioni notevole.)

(Mi consiglia di non usare Selectcome un foreachciclo per eseguire un'azione effettiva mutazione su un elemento di un insieme, il modo in cui avete fatto nei vostri tentativi di refactoring. Si può fare, ma le persone saranno sorpresi dal codice, e ti devono tenere valutazione pigra a mente tutto il tempo.)

Risposto il 28/03/2009 a 22:59
fonte dall'utente

voti
9

Va bene, non è chiaro esattamente ciò che IMapperha in sé, ma mi piacerebbe suggerire alcune cose, alcuni dei quali non può essere fattibile a causa di altre restrizioni. Ho scritto questo fuori più o meno come ho pensato - Penso che aiuta a vedere la linea di pensiero in azione, come che ti rendono più facile per voi di fare la stessa cosa la prossima volta. (Supponendo che si piacciono le mie soluzioni, ovviamente :)

LINQ è intrinsecamente funzionale in stile. Ciò significa che idealmente, le query non dovrebbero avere effetti collaterali. Per esempio, mi aspetto un metodo con una firma di:

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

per restituire una nuova sequenza di oggetti utente con informazioni aggiuntive, piuttosto che mutando gli utenti esistenti. L'unica informazione che si sta aggiungendo è l'avatar, quindi mi piacerebbe aggiungere un metodo a Userlungo le linee di:

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

Si potrebbe anche voler fare Userpienamente immutabili - ci sono varie strategie intorno a quello, come il builder. Chiedimi se volete maggiori dettagli. In ogni caso, la cosa principale è che abbiamo creato un nuovo utente che è una copia di quello vecchio, ma con l'avatar specificato.

Primo tentativo: inner join

Ora di nuovo al vostro mapper ... hai attualmente ottenuto tre pubblici metodi ma la mia ipotesi è che solo il primo deve essere pubblico, e che il resto delle API in realtà non ha bisogno di esporre gli utenti di Facebook. Sembra che il tuo GetFacebookUsersmetodo è sostanzialmente a posto, anche se probabilmente sarei allineare la query in termini di spazio bianco.

Quindi, data una sequenza di utenti locali e una collezione di utenti di Facebook, non ci resta a fare il bit di mappatura reale. Una clausola "join" retta è problematico, perché non produrrà gli utenti locali che non dispongono di un utente di Facebook corrispondenti. Invece, abbiamo bisogno di qualche modo di trattare un utente non-Facebook, come se fossero un utente di Facebook, senza un avatar. Essenzialmente questo è il modello oggetto nullo.

Possiamo farlo da venire con un utente di Facebook che ha un uid null (supponendo che il modello a oggetti permette che):

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

Tuttavia, vogliamo davvero una sequenza di questi utenti, perché è quello che Enumerable.Concatutilizza:

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

Ora possiamo semplicemente "aggiungere" questa voce fittizia per la nostra vera, e ti unisci a una normale interiore. Si noti che questo presuppone che la ricerca degli utenti di Facebook sarà sempre trovare un utente per qualsiasi "vero" Facebook UID. Se questo non è il caso, avremmo bisogno di rivisitare questo e non utilizzare un inner join.

Includiamo l'utente "null", alla fine, poi fare il join e progetto utilizzando 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);
}

Così la classe completa sarebbe:

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

Alcuni punti qui:

  • Come notato in precedenza, il join interno diventa problematico se Facebook UID di un utente potrebbe non essere recuperato come utente valido.
  • Allo stesso modo otteniamo problemi se abbiamo gli utenti duplicati Facebook - ogni utente locale finirebbe per uscire due volte!
  • Questo sostituisce (rimuove) l'avatar per gli utenti non-Facebook.

Un secondo approccio: gruppo Partecipa

Vediamo se siamo in grado di affrontare questi punti. Darò per scontato che se abbiamo recuperati più utenti di Facebook per un singolo Facebook UID, quindi non importa quale di essi ci afferra l'avatar da - dovrebbero essere gli stessi.

Quello che ci serve è un gruppo di unirsi, in modo che per ogni utente locale si ottiene una sequenza di corrispondenza utenti di Facebook. Provvederemo quindi utilizziamo DefaultIfEmptyper rendere la vita più facile.

Possiamo continuare a WithAvatarcome era prima - ma questa volta stiamo solo andando a chiamarlo se abbiamo un utente di Facebook per afferrare l'avatar da. Un gruppo si uniscono in C # espressioni di query è rappresentato da join ... into. Questa query è ragionevolmente lungo, ma non è troppo spaventoso, onesto!

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

Ecco l'espressione di query di nuovo, ma con i commenti:

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

La soluzione non LINQ

Un'altra opzione è quella di utilizzare solo LINQ leggermente. Per esempio:

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

Questo utilizza un blocco iteratore invece di un'espressione di query LINQ. ToDictionarysarà un'eccezione se riceve due volte la stessa chiave - una possibilità per risolvere questo è quello di cambiare GetFacebookUsersper assicurarsi che sembra solo per gli ID distinte:

    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
    }

Che assume il servizio di web funziona in modo appropriato, ovviamente - ma se così non fosse, probabilmente vuole un'eccezione comunque :)

Conclusione

Fate la vostra scelta dei tre. Il gruppo di join è probabilmente più difficile da capire, ma si comporta meglio. La soluzione blocco iteratore è forse il più semplice, e dovrebbe comportarsi bene con la GetFacebookUsersmodifica.

Fare Userimmutabile sarebbe quasi certamente un passo positivo però.

Un bel sottoprodotto di tutte queste soluzioni è che gli utenti escono nello stesso ordine sono andati in. Che non può bene essere importante per voi, ma può essere un bell'albergo.

Spero che questo aiuti - è stato una domanda interessante :)

EDIT: E 'la mutazione la strada da percorrere?

Dopo aver visto nei vostri commenti che il tipo di utente locale è in realtà un tipo di entità da Entity Framework, che potrebbe non essere appropriato per prendere questa linea di condotta. Rendere più immutabile è praticamente fuori questione, e ho il sospetto che la maggior parte degli usi di tipo si aspettano mutazione.

Se questo è il caso, può essere utile cambiare l'interfaccia per far sì che più chiaro. Invece di restituire un IEnumerable<User>(che implica - in qualche misura - proiezione) si potrebbe desiderare di cambiare sia la firma e il nome, lasciando con qualcosa di simile a questo:

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

Ancora una volta, questa non è una soluzione particolarmente "LINQ-y" (in l'operazione principale) più - ma questo è ragionevole, in quanto non sei realmente "interrogazione"; si sta "l'aggiornamento".

Risposto il 01/04/2009 a 20:28
fonte dall'utente

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