LINQ do mapowania profilu na Facebooku z moim informacji o użytkowniku

głosy
3

Po przeczytaniu książki na LINQ myślę o ponowne napisanie klasy mapper, które napisałem w C #, aby użyć LINQ. Zastanawiam się, czy ktoś może mi pomóc. Uwaga: jest to nieco kłopotliwe, ale obiekt użytkownika jest lokalnym użytkownika i użytkownik (małe litery) jest przedmiotem generowane z Facebooka XSD.

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

Moje pierwsze dwie próby hit ściany

próba 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;
}

próba 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);
}
Utwórz 02/03/2009 o 20:32
źródło użytkownik
W innych językach...                            


2 odpowiedzi

głosy
5

Byłbym skłonny napisać coś takiego zamiast:

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

Jednak nie będę twierdzić, że była duża poprawa w stosunku do tego, co masz (chyba, zbiory podręcznik użytkownika lub facebook są bardzo duże, w takim przypadku można skończyć z noticable różnicy wydajności).

(Polecam przed użyciem Selectjak foreachpętli wykonać rzeczywiste działanie mutacjom na element zestawu, sposób, w jaki zrobił w swoich próbach refaktoryzacji. Można to zrobić, ale ludzie będą zaskoczeni kodzie, i będziesz trzeba zachować ocenę leniwe pamiętać przez cały czas).

Odpowiedział 28/03/2009 o 22:59
źródło użytkownik

głosy
9

Dobrze, że nie jest jasne, co dokładnie IMapperma się w niej, ale chciałbym zaproponować kilka rzeczy, z których część może być niewykonalne z powodu innych ograniczeń. Pisałem to się dość dużo jak myślałam o tym - myślę, że to pomaga, aby zobaczyć tok myślenia w działaniu, jako że będzie łatwiej, aby zrobić to samo następnym razem. (Zakładając, że lubisz moje rozwiązania, oczywiście :)

LINQ jest z natury funkcjonalnej w wielkim stylu. Oznacza to, że idealnie, zapytań nie powinien mieć skutki uboczne. Na przykład, będę oczekiwać metodę z podpisem:

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

aby powrócić nową sekwencję obiektów użytkowników z dodatkowymi informacjami, zamiast mutacji istniejących użytkowników. Jedyne informacje aktualnie dołączanie jest offline, więc chciałbym dodać metodę w Userwzdłuż linii:

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

Można nawet chcą, aby Usercałkowicie niezmienna - istnieją różne strategie wokół których, takie jak wzór budowniczy. Zapytaj mnie jeśli chcesz więcej szczegółów. W każdym razie, najważniejsze jest to, że stworzyliśmy nowy użytkownik, który jest kopią starego, ale z określonym awatarze.

Pierwsza próba: sprzężenie wewnętrzne

Teraz z powrotem do swojej odwzorowującym ... masz obecnie dostał trzy publiczne metody, ale moje przypuszczenie jest to, że tylko pierwszy z nich musi być publiczne, a reszta API faktycznie nie trzeba narażać użytkowników Facebooka. Wygląda na to, GetFacebookUsersmetoda jest w zasadzie w porządku, chociaż pewnie bym kolejce zapytania pod względem spacji.

Tak więc, biorąc pod uwagę kolejność lokalnych użytkowników oraz zbiór użytkowników Facebooka, jesteśmy w lewo robi rzeczywistego odwzorowania bitów. Prosta „join” klauzula jest problematyczne, ponieważ nie przyniesie lokalnych użytkowników, które nie mają pasujący Facebook użytkownikowi. Zamiast tego, musimy w jakiś sposób leczenia użytkownik inny niż Facebook, jak gdyby były użytkownik Facebook bez awatara. Zasadniczo jest to pusty obiekt.

Możemy to zrobić poprzez wymyślanie z użytkownikiem Facebooka, który ma uid zerowej (zakładając model obiektowy pozwala na to):

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

Jednak faktycznie chcą sekwencję tych użytkowników, ponieważ to, co Enumerable.Concatwykorzystuje:

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

Teraz możemy po prostu „add” to fikcyjny wpis do naszego prawdziwego, a nie normalny wewnętrzna przyłączyć. Zauważ, że ta zakłada, że wyszukiwanie użytkowników Facebook będzie zawsze znaleźć użytkownika dla każdego „prawdziwego” Facebook UID. Jeśli to nie jest przypadek, że musimy zrewidować to i nie wykorzystywać sprzężenie wewnętrzne.

Mamy zawierać „null” użytkownikowi na końcu, a następnie wykonaj dołączyć i projekt za pomocą 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);
}

Tak pełna klasa będzie:

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

Kilka punktów tutaj:

  • Jak wspomniano wcześniej, sprzężenie wewnętrzne staje się problematyczne, jeśli użytkownik jest Facebook UID nie mogą być pobrane jako ważnego użytkownika.
  • Podobnie mamy problemy, jeśli mamy duplikaty użytkowników Facebooka - każdy lokalny użytkownik może skończyć wychodzi dwa razy!
  • Zastępuje (Usuwa) awatara dla użytkowników innych niż Facebook.

Drugie podejście: Grupa przyłączenia

Zobaczmy, czy uda nam się rozwiązać te kwestie. Będę zakładać, że jeśli mamy pobranych wielu użytkowników Facebooka za pomocą pojedynczego Facebooku UID, to nie ma znaczenia, który z nich możemy chwycić awatara z - powinny być takie same.

To czego potrzebujemy to grupa dołączyć, tak, że dla każdego użytkownika lokalnego otrzymujemy sekwencję dopasowanie użytkowników Facebooka. Będziemy wtedy użyć DefaultIfEmpty, by ułatwić życie.

Możemy zachować WithAvatarjak to było wcześniej - ale tym razem jesteśmy tylko będzie to nazwać, jeśli mamy użytkownikowi Facebook, aby pobrać z awatara. Grupa dołączyć w C # wyrażeń zapytań jest reprezentowana przez join ... into. To zapytanie jest dość długa, ale to nie jest zbyt przerażające, uczciwy!

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

Oto wyrażenie kwerendy ponownie, ale z opinii:

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

Non-LINQ rozwiązanie

Inną opcją jest tylko używać LINQ bardzo nieznacznie. Na przykład:

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

Wykorzystuje blok iteracyjnej zamiast wyrażenie zapytania LINQ. ToDictionarywygeneruje wyjątek, jeśli otrzyma dwukrotnie tego samego klucza - jedna opcja, aby obejść to zmienić GetFacebookUsers, aby upewnić się, że patrzy tylko na odrębnych identyfikatorów:

    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
    }

Który zakłada, że ​​serwis internetowy działa prawidłowo, oczywiście - ale jeśli tak nie jest, prawdopodobnie chcesz, aby rzucić wyjątek w każdym razie :)

Wniosek

Wybieraj spośród trzech. Grupa dołączyć to prawdopodobnie najtrudniejsza do zrozumienia, ale zachowuje się najlepiej. Roztwór blok iteracyjnej jest możliwie najprostszy i powinna zachowywać się dobrze z GetFacebookUsersmodyfikacji.

Dokonywanie Userniezmienne prawie na pewno będzie pozytywny krok choć.

Jeden miły ubocznym wszystkich tych rozwiązań jest to, że użytkownicy wyjdzie w tej samej kolejności, w jakiej stał. To może nie być dla ciebie ważne, ale może to być ładny obiekt.

Nadzieję, że to pomaga - to było ciekawe pytanie :)

EDIT: Czy mutacja droga?

Widząc w swoich komentarzach, że lokalna Typ użytkownika jest rzeczywiście typu jednostki z Entity Framework, to może nie być odpowiedni do podjęcia tego postępowania. Dzięki czemu jest prawie niezmienna wchodzi w rachubę, i podejrzewam, że większość zastosowań typu będą oczekiwać mutacji.

Jeśli to przypadek, może warto zmieniając interfejs, aby to jaśniejsze. Zamiast odesłanie IEnumerable<User>(co oznacza - w pewnym stopniu - projekcja) może chcesz zmienić zarówno podpis i nazwę, dzięki czemu można z czegoś takiego:

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

Ponownie, nie jest to szczególnie „LINQ-y” rozwiązanie (w podstawowej operacji) więcej - ale to jest uzasadnione, jako że nie jesteś naprawdę „odpytywanie”; jesteś „aktualizację”.

Odpowiedział 01/04/2009 o 20:28
źródło użytkownik

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