Pomocí LINQ mapovat Facebook profil se svým uživatelským info

hlasů
3

Po přečtení knihy o LINQ Přemýšlím o přepisování třídu mapovač, který jsem napsal v jazyce C # používat LINQ. Zajímalo by mě, jestli někdo mi může pomoct. Poznámka: jeho trochu matoucí, ale objekt User je místní uživatel a uživatel (malé) je objekt vytvořen z Facebooku XSD.

původní 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 první dva pokusy zasáhnout stěny

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

pokus 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);
}
Položena 02/03/2009 v 20:32
zdroj uživatelem
V jiných jazycích...                            


2 odpovědí

hlasů
5

Já bych se přikláněl k napsat něco takového místo:

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

Nicméně bych se tvrdit, že bylo velké zlepšení přes to, co máte (pokud uživatel nebo facebook uživatelské kolekce jsou velmi velké, v takovém případě byste mohli skončit s rozdílem nápadný výkon).

(Já bych doporučil proti použití Selectjako foreachsmyčky provést skutečnou mutovat akci na prvek souboru, jako vy ve svých pokusech o refaktorování. Můžete to udělat, ale lidé budou překvapeni kódu, a budete mít na lenivé ohodnocení na mysli po celou dobu.)

Odpovězeno 28/03/2009 v 22:59
zdroj uživatelem

hlasů
9

Dobře, to není jasné, co přesně IMappermá v něm, ale já bych navrhnout několik věcí, z nichž některé nemusí být proveditelné v důsledku dalších omezení. Napsal jsem na to docela hodně, jak jsem o tom přemýšlel - myslím, že to pomůže vidět myšlenek v akci, protože to bude dělat to pro vás jednodušší udělat to samé příště. (Za předpokladu, že se vám líbí moje řešení, samozřejmě :)

LINQ je ve své podstatě funkční ve velkém stylu. To znamená, že v ideálním případě, dotazy by neměly mít vedlejší účinky. Například, já bych očekávat metodu s podpisem:

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

vrátit novou sekvenci uživatelských objektů s dalšími informacemi, nikoli mutací stávajících uživatelů. Jediná informace, kterou právě připojením je avatar, takže bych přidat metodu Userv duchu:

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

Dalo by se dokonce chtít, aby se Userzcela neměnná - existují různé strategie, kolem které, jako Builder. Zeptejte se mě, jestli budete chtít více informací. Každopádně, hlavní věc je, že jsme vytvořili nový uživatel, který je kopií toho starého, ale s daným avataru.

První pokus: vnitřní spojení

Nyní zpět do mapper ... jste v současné době má tři veřejné metody, ale můj odhad je, že pouze první z nich musí být veřejná a že zbytek API není ve skutečnosti je třeba vystavit uživatele Facebooku. Vypadá to, že vaše GetFacebookUsersmetoda je v podstatě v pořádku, i když bych asi seřadí dotaz, pokud jde o mezery.

Takže vzhledem k tomu, sled místních uživatelů a sbírka uživatelů Facebooku, my jsme opustili dělat skutečné mapování bitu. rovný „spojit“ Klauzule je problematické, protože nepřinese místní uživatele, které nemají odpovídající uživatele Facebooku. Namísto toho potřebujeme nějaký způsob léčení uživatele non-Facebook, jako kdyby se jednalo o uživateli Facebooku bez avataru. V podstatě se jedná o null object.

Můžeme to udělat, když přišel s uživatelem Facebooku, který má nulový uid (za předpokladu, objektový model umožňuje, aby):

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

Nicméně, jsme vlastně chtějí sled těchto uživatelů, protože to je to, co Enumerable.Concatpoužívá:

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

Nyní můžeme jednoduše „add“ tento fiktivní vstup do našeho skutečného jeden, a to normální vnitřní spojení. Všimněte si, že to předpokládá, že vyhledávání uživatelů Facebooku se vždy najde uživatel na jakémkoliv „skutečné“ Facebook UID. Pokud to není tento případ, že se musíme vrátit to, a ne pouze vnitřní spojení.

My patří „null“ uživatel na konci, pak se spoji a projekt za použití 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že plná třída bude vypadat následovně:

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

Několik bodů zde:

  • Jak již bylo uvedeno dříve, vnitřní spojení se stává problematické, pokud uživatele Facebooku UID nemusí být přitažené za vlasy jako platný uživatel.
  • Stejně tak dostaneme problémy, pokud máme duplicitní uživatelů Facebooku - každý lokální uživatel skončí vyjde dvakrát!
  • Nahrazuje (Odstraní) avatar pro uživatele non-Facebook.

Druhý přístup: skupina připojit

Uvidíme, jestli se můžeme zabývat těmito body. Budu předpokládat, že pokud máme přitažené za vlasy více uživatelů Facebooku na jednom Facebook UID, pak nezáleží na tom, který z nich jsme urvat Avatar od - měly by být stejné.

Co potřebujeme, je skupina spojit, takže pro každou místní uživatele dostaneme posloupnost odpovídající uživatelů Facebooku. Budeme pak použít DefaultIfEmpty, aby ulehčila život.

Můžeme si ponechat WithAvatar, jak to bylo dříve - ale tentokrát jsme jen bude říkat, jestli máme uživatele Facebooku chytit avatar z. Skupina připojit C # výrazů dotazu je představován join ... into. Tento dotaz je poměrně dlouhá, ale to není příliš děsivé, upřímný!

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

Zde je výraz dotazu znovu, ale s komentářem:

// "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 roztok

Další možností je použít pouze LINQ velmi nepatrně. Například:

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

Používá iterátor blok namísto dotazovací výraz LINQ. ToDictionarybude hodit výjimku, pokud obdrží dvakrát stejný klíč - jednou z možností, jak vyřešit tento je změnit GetFacebookUsers, aby se ujistil, že vypadá pouze na odlišné ID:

    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
    }

Že předpokládá, že webová služba pracuje odpovídajícím způsobem, samozřejmě - ale pokud tomu tak není, budete pravděpodobně chtít, aby v každém případě vyvolat výjimku :)

Závěr

Vyberte si ze tří. Skupina spojení je pravděpodobně nejtěžší pochopit, ale chová nejlépe. Iterátor blok řešení je možná nejjednodušší a chovat v pořádku s GetFacebookUsersmodifikací.

Tvorba Userneměnný by téměř jistě bylo pozitivním krokem ačkoli.

Jedna pěkná vedlejším produktem všech těchto řešení je, že uživatelé vyjdou ve stejném pořadí, v jakém šel. To může také nemusí být pro vás důležité, ale může to být pěkná vlastnost.

Doufám, že to pomůže - už je to zajímavá otázka :)

EDIT: Je mutace způsob, jak jít?

Poté, co viděl ve svých komentářích, že místní typ uživatele je vlastně typ entity z rámce osoby, to může být nevhodné, aby se tento postup. Díky tomu je neměnný, je do značné míry vyloučeno, a mám podezření, že většina použití typu budou očekávat mutaci.

Pokud tomu tak je, může to být stojí za změnou svého rozhraní, aby to jasnější. Namísto vrácení IEnumerable<User>(z čehož vyplývá - do jisté míry - projekce) budete chtít změnit i podpis a jméno, takže vám něco takového:

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

Opět platí, že to není nijak zvlášť „LINQ-y“ řešení (v hlavním provozu) nic víc - ale to je rozumné, protože nejste opravdu „dotazování“; jste „aktualizace“.

Odpovězeno 01/04/2009 v 20:28
zdroj uživatelem

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