LINQ à la carte de profil facebook avec mes informations utilisateur

voix
3

Après avoir lu un livre sur LINQ Je pense à réécrivant une classe mappeur que j'ai écrit en C # à utiliser LINQ. Je me demande si quelqu'un peut me donner un coup de main. Remarque: il est un peu confus, mais l'objet utilisateur est l'utilisateur local et l'utilisateur (en minuscules) est l'objet généré par le 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;
    }
}

Mes deux premières tentatives ont frappé les murs

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

tentative 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);
}
Créé 02/03/2009 à 20:32
source utilisateur
Dans d'autres langues...                            


2 réponses

voix
5

Je serais enclin à écrire quelque chose comme ceci:

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

Cependant, je ne prétends pas que c'était une grande amélioration par rapport à ce que vous avez (à moins que les collections de l'utilisateur ou l'utilisateur facebook sont très grandes, dans ce cas, vous pourriez vous retrouver avec une différence de performances de manière significative.)

(Je vous recommande de ne pas utiliser Selectcomme une foreachboucle pour effectuer une action réelle sur un muter élément d'un ensemble, la façon dont vous avez fait dans vos tentatives de refactoring. Vous pouvez le faire, mais les gens seront surpris par votre code, et vous doivent garder à l' esprit l' évaluation paresseuse tout le temps.)

Créé 28/03/2009 à 22:59
source utilisateur

voix
9

D' accord, on ne sait pas exactement ce qui IMappera en elle, mais je vous suggère quelques petites choses, dont certains peuvent ne pas être possible en raison d'autres restrictions. J'ai écrit ceci à peu près comme je l' ai pensé à ce sujet - je pense que ça aide à voir le train de la pensée en action, car ce sera plus facile pour vous de faire la même chose la prochaine fois. ( En supposant que vous aimez mes solutions, bien sûr :)

LINQ est par nature fonctionnelle dans le style. Cela signifie que, idéalement, les requêtes ne doivent pas avoir des effets secondaires. Par exemple, je pense une méthode avec une signature de:

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

pour retourner une nouvelle séquence d'objets utilisateur avec des informations supplémentaires, plutôt que les utilisateurs existants muter. Les informations que vous êtes actuellement le annexant avatar, donc j'ajouter une méthode dans le Userlong des lignes 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;
}

Vous voudrez peut - être même de faire Userpleinement immuable - il existe différentes stratégies autour que, comme le modèle de constructeur. Demandez - moi si vous voulez plus de détails. Quoi qu'il en soit, la chose principale est que nous avons créé un nouvel utilisateur qui est une copie de l'ancien, mais avec l'avatar spécifié.

Première tentative: jointure interne

Maintenant , revenons à votre mappeur ... vous avez actuellement obtenu trois publics méthodes , mais ma conjecture est que seul le premier doit être public, et que le reste de l'API ne pas réellement besoin d'exposer les utilisateurs de Facebook. Il semble que votre GetFacebookUsersméthode est fondamentalement correct, bien que j'aligne probablement la requête en termes de espaces.

Donc, étant donné une séquence d'utilisateurs locaux et une collection d'utilisateurs de Facebook, nous sommes laissés faire le bit de cartographie réelle. Une clause « join » droite est problématique, car elle ne donnera pas les utilisateurs locaux qui ne disposent pas d'un correspondant à l'utilisateur Facebook. Nous avons plutôt besoin d'une certaine façon de traiter un utilisateur non-Facebook comme si elles étaient un utilisateur Facebook sans un avatar. Essentiellement, cela est le modèle d'objet null.

Nous pouvons le faire en venant avec un utilisateur Facebook qui a un uid null (en supposant que le modèle d'objet permet que):

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

Cependant, nous voulons en fait une séquence de ces utilisateurs, parce que ce Enumerable.Concatutilise:

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

Maintenant , nous pouvons simplement « ajouter » cette entrée factice à notre vrai, et faire une jointure interne normale. Notez que cela suppose que la recherche des utilisateurs de Facebook toujours trouver un utilisateur pour tout « réel » Facebook UID. Si ce n'est pas le cas, il nous faudrait revoir cela et ne pas utiliser une jointure interne.

Nous incluons l'utilisateur « null » à la fin, alors ne le joindre et projet en utilisant 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);
}

Ainsi, la classe complète serait:

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

Quelques points ici:

  • Comme indiqué précédemment, la jointure interne devient problématique si UID Facebook d'un utilisateur pourrait ne pas être récupéré en tant qu'utilisateur valide.
  • De même, nous avoir des problèmes si nous avons des utilisateurs en double Facebook - chaque utilisateur local finirait par sortir deux fois!
  • Cela remplace (supprime) l'avatar pour les utilisateurs non-Facebook.

Une seconde approche: groupe Rejoindre

Voyons voir si nous pouvons répondre à ces points. Je suppose que si nous avons récupérés plusieurs utilisateurs de Facebook pour un seul UID Facebook, il ne nous saisissons pas d' importance d'entre eux l'avatar de - ils devraient être les mêmes.

Ce que nous avons besoin est un groupe se joindre, de sorte que pour chaque utilisateur local , nous obtenons une séquence d'appariement des utilisateurs de Facebook. Nous allons ensuite utiliser DefaultIfEmptypour rendre la vie plus facile.

Nous pouvons garder WithAvatarcomme avant - mais cette fois , nous allons seulement appeler si nous avons un utilisateur Facebook pour saisir l'avatar de. Un groupe se joindre à C # expressions de requête est représentée par join ... into. Cette requête est assez longue, mais il est pas trop effrayant, honnête!

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

Voici à nouveau l'expression de requête, mais avec des commentaires:

// "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 solution non-LINQ

Une autre option consiste à utiliser uniquement LINQ très légèrement. Par exemple:

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

Celui - ci utilise un bloc itérateur au lieu d'une expression de requête LINQ. ToDictionaryva lancer une exception si elle reçoit la même touche deux fois - une option pour contourner est de changer GetFacebookUserspour vous assurer qu'il ne recherche que les ID distincts:

    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
    }

Cela suppose le service Web fonctionne correctement, bien sûr - mais si elle ne le fait pas, vous voulez probablement lancer une exception de toute façon :)

Conclusion

Faites votre choix parmi les trois. Le groupe est probablement rejoindre le plus difficile à comprendre, mais se comporte mieux. La solution de bloc itérateur est peut - être le plus simple, et doit se comporter d' accord avec la GetFacebookUsersmodification.

Faire Userimmuable serait presque certainement une étape positive cependant.

Un joli sous-produit de toutes ces solutions est que les utilisateurs sortent dans le même ordre dans lequel ils sont allés. Ce pourrait bien ne pas être important pour vous, mais il peut être une belle propriété.

Espérons que cela aide - il a été une question intéressante :)

EDIT: Est mutation la voie à suivre?

Après avoir vu dans vos commentaires que le type d'utilisateur local est en fait un type d'entité du cadre de l' entité, il peut ne pas être approprié de suivre ce cours d'action. Rendant immuable est à peu près hors de question, et je soupçonne que la plupart des utilisations du type vont attendre la mutation.

Si tel est le cas, il peut être utile de changer votre interface pour la rendre plus claire que. Au lieu de retourner un IEnumerable<User>( ce qui implique - dans une certaine mesure - projection) , vous voudrez peut - être changer à la fois la signature et le nom, vous laissant avec quelque chose comme ceci:

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

Encore une fois, ce n'est pas une solution particulièrement « LINQ-y » (dans l'opération principale) plus - mais qui est raisonnable, comme vous n'êtes pas vraiment « interrogation »; vous « mettre à jour ».

Créé 01/04/2009 à 20:28
source utilisateur

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