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“.