Ok, det er ikke klart nøyaktig hva IMapperhar i det, men jeg vil foreslå et par ting, noen som kanskje ikke er gjennomførbart på grunn av andre restriksjoner. Jeg har skrevet dette ut ganske mye som jeg har tenkt på det - jeg tror det hjelper å se tankerekken i aksjon, som det vil gjøre det lettere for deg å gjøre det samme neste gang. (Forutsatt at du liker mine løsninger, selvfølgelig :)
LINQ er iboende funksjonell stil. Det betyr at ideelt sett, spørsmål bør ikke ha bivirkninger. For eksempel, jeg forventer en metode med en signatur av:
public IEnumerable<User> MapFrom(IEnumerable<User> users)
å returnere en ny sekvens av brukerobjekter med ekstra informasjon, heller enn å mutere eksisterende brukere. Den eneste informasjonen du nå føye er avataren, så jeg vil legge til en metode i Userretning av:
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;
}
Du kan selv ønsker å lage Userfullt uforanderlige - det finnes ulike strategier rundt dette, slik som byggmester mønster. Spør meg hvis du vil ha mer informasjon. Uansett, er det viktigste at vi har opprettet en ny bruker som er en kopi av den gamle, men med den angitte avatar.
Første forsøk: inner delta
Nå tilbake til mapper ... du har nå fått tre offentlige metoder, men min gjetning er at bare det første man trenger å være offentlig, og at resten av API faktisk ikke trenger å utsette Facebook-brukere. Det ser ut til at GetFacebookUsersmetoden er i utgangspunktet greit, selv om jeg ville trolig stille opp spørsmålet når det gjelder mellomrom.
Så, gitt en sekvens av lokale brukere og en samling av Facebook-brukere, vi forlot gjør selve kartleggingen bit. En rett "join" -klausulen er problematisk, fordi det ikke vil gi de lokale brukere som ikke har en matchende Facebook-bruker. I stedet må vi noen måte å behandle en ikke-Facebook-bruker som om de var en Facebook-bruker uten en avatar. I hovedsak er dette nullgjenstandsmønsteret.
Vi kan gjøre det ved å komme opp med en Facebook-bruker som har en null uid (forutsatt objektmodellen gjør det):
// Adjust for however the user should actually be constructed.
private static readonly FacebookUser NullFacebookUser = new FacebookUser(null);
Men vi faktisk ønsker en sekvens av disse brukerne, fordi det er det Enumerable.Concatbruker:
private static readonly IEnumerable<FacebookUser> NullFacebookUsers =
Enumerable.Repeat(new FacebookUser(null), 1);
Nå kan vi bare "legge" dette dummy innreise til vår ekte en, og gjøre en normal indre delta. Merk at dette forutsetter at oppslag av Facebook-brukere vil alltid finne en bruker for noen "ekte" Facebook UID. Hvis det ikke er tilfelle, ville vi trenger å se dette, og ikke bruke en indre delta.
Vi inkludere "null" bruker på slutten, gjør deretter delta og prosjekt med 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);
}
Så hele klassen vil være:
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
}
}
Noen punkter her:
- Som nevnt før, det indre delta blir problematisk hvis en brukers Facebook UID kanskje ikke hentet som en gyldig bruker.
- Tilsvarende får vi problemer hvis vi har like Facebook-brukere - hver lokal bruker ville ende opp som kommer ut to ganger!
- Dette erstatter (fjerner) avataren for ikke-Facebook-brukere.
En annen fremgangsmåte: gruppe bli
La oss se om vi kan løse disse punktene. Jeg vil anta at hvis vi har hentet flere Facebook-brukere for en enkelt Facebook UID, så det spiller ingen rolle hvilken av dem vi ta tak i avatar fra - de bør være den samme.
Det vi trenger er en gruppe delta, slik at det for hver lokal bruker får vi en sekvens av matchende Facebook-brukere. Vi vil da bruke DefaultIfEmptyfor å gjøre livet enklere.
Vi kan holde WithAvatarsom det var før - men denne gangen vi bare kommer til å kalle det hvis vi har en Facebook-bruker å ta tak i avatar fra. En gruppe delta i C # spørreuttrykk er representert med join ... into. Dette søket er rimelig lang, men det er ikke så skummelt, ærlig!
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);
}
Her er spørsmålet uttrykket igjen, men med kommentarer:
// "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);
Den ikke-LINQ løsning
Et annet alternativ er å bare bruke LINQ svært svakt. For eksempel:
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;
}
}
}
Denne benytter en iteratorblokk i stedet for en spørring LINQ uttrykk. ToDictionaryvil kaste et unntak hvis den mottar den samme nøkkelen to ganger - en mulighet til å omgå dette på er å endre GetFacebookUsersfor å sørge for at det bare ser for forskjellige IDer:
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
}
Det foruts nettet tjenesten fungerer på riktig måte, selvfølgelig - men hvis det ikke gjør det, har du sannsynligvis ønsker å kaste et unntak likevel :)
Konklusjon
Ta plukke ut av de tre. Gruppen delta er sannsynligvis vanskeligst å forstå, men oppfører beste. Den iteratorblokk Løsningen er muligens det enkleste, og bør oppføre seg greit med GetFacebookUsersmodifikasjon.
Making Useruforanderlige ville nesten helt sikkert være et positivt skritt skjønt.
En fin biprodukt av alle disse løsningene er at brukerne kommer ut i samme rekkefølge som de gikk i. Det kan vel ikke være viktig for deg, men det kan være en fin egenskap.
Håper dette hjelper - det har vært et interessant spørsmål :)
EDIT: Er mutasjon veien å gå?
Etter å ha sett i dine kommentarer at det lokale bruker typen er faktisk en enhet type fra Entity Framework, det kan ikke være riktig å ta dette kurset av handlingen. Gjør det uforanderlige er ganske mye ut av spørsmålet, og jeg mistenker at de fleste anvendelser av typen vil forvente mutasjon.
Hvis det er tilfelle, kan det være verdt å endre grensesnittet for å gjøre det klarere. I stedet for å returnere en IEnumerable<User>(som tilsier - til en viss grad - projeksjon) kan det være lurt å endre både signatur og navnet, og du har noe sånt som dette:
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
}
}
Igjen, dette er ikke en spesielt "LINQ-y" løsning (i hoved drift) noe mer - men det er rimelig, som du egentlig ikke "spørring"; du "oppdatering".