Oké, het is niet precies duidelijk wat IMapperin zich heeft, maar ik zou een paar dingen, waarvan sommige misschien niet haalbaar te wijten zijn aan andere beperkingen te stellen. Ik heb dit uitgeschreven min of meer zoals ik heb nagedacht over het - ik denk dat het helpt om de gedachtengang in actie te zien, want dat zal het gemakkelijker maken voor u om hetzelfde te doen de volgende keer. (Ervan uitgaande dat je van mijn oplossingen, natuurlijk :)
LINQ is van nature functioneel in stijl. Dat betekent dat idealiter queries mag niet bijwerkingen hebben. Bijvoorbeeld, zou ik verwachten dat een methode met een handtekening van:
public IEnumerable<User> MapFrom(IEnumerable<User> users)
een nieuwe reeks gebruikersobjecten keren met extra informatie, in plaats van het muteren van de huidige gebruiker. De enige informatie die u momenteel het toevoegen is de avatar, dus ik zou een methode toe te voegen in Userde trant van:
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;
}
Je zou zelfs willen maken Uservolledig onveranderlijk - er zijn verschillende strategieën rond dat, zoals de bouwer patroon. Vraag me als je wilt meer informatie. Hoe dan ook, het belangrijkste is dat we een nieuwe gebruiker die een kopie is van de oude hebt gemaakt, maar met de opgegeven avatar.
Eerste poging: inner join
Nu terug naar uw mapper ... je hebt momenteel nog drie publieke methoden, maar mijn gok is dat alleen de eerste die moet openbaar zijn, en dat de rest van de API niet echt nodig om de Facebook-gebruikers bloot te leggen. Het lijkt erop dat uw GetFacebookUsersmethode is in principe goed, hoewel ik zou waarschijnlijk line-up van de vraag in termen van witruimte.
Dus, gegeven een reeks van lokale gebruikers en een verzameling van de Facebook-gebruikers, zijn we vertrokken het doen van de werkelijke mapping bit. Een straight "join" clausule is problematisch, omdat het niet de lokale gebruikers die niet een bijpassende Facebook gebruiker hebben zal opleveren. In plaats daarvan moeten we een of andere manier van het behandelen van een niet-Facebook-gebruiker alsof ze een Facebook-gebruiker zonder een avatar. In wezen is dit de null object patroon.
We kunnen dat doen door te komen met een Facebook-gebruiker die een null uid heeft (ervan uitgaande dat het object model maakt het mogelijk dat):
// Adjust for however the user should actually be constructed.
private static readonly FacebookUser NullFacebookUser = new FacebookUser(null);
Maar we willen eigenlijk een opeenvolging van deze gebruikers, want dat is wat Enumerable.Concatgebruik maakt van:
private static readonly IEnumerable<FacebookUser> NullFacebookUsers =
Enumerable.Repeat(new FacebookUser(null), 1);
Nu kunnen we alleen maar "add" deze dummy toegang tot onze echte, en doe een normale inner join. Merk op dat dit gaat ervan uit dat de lookup van Facebook-gebruikers altijd een gebruiker voor een "echte" Facebook UID te vinden. Als dat niet het geval is, zouden we nodig hebben om dit te herzien en niet gebruik maken van een inner join.
We nemen de "nul" gebruiker aan het einde, doe dan het te sluiten en project met behulp van 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);
}
Dus de volledige klas zou zijn:
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
}
}
Een paar punten hier:
- Zoals eerder opgemerkt, de inner join wordt problematisch wanneer een gebruiker Facebook UID niet zou kunnen worden gehaald als een geldige gebruiker.
- Op dezelfde manier krijgen we problemen als we dubbele Facebook-gebruikers - elke lokale gebruiker zou uiteindelijk coming out twee keer!
- Dit vervangt (verwijdert) de avatar voor niet-Facebook-gebruikers.
Een tweede benadering: groep aan te sluiten
Laten we eens kijken of we deze punten kunnen pakken. Ik ga ervan uit dat als we hebben opgehaald meerdere Facebook-gebruikers voor een enkele Facebook UID, dan maakt het niet uit wie van hen we pak de avatar uit - ze moeten hetzelfde zijn.
Wat we nodig hebben is een groep aan te sluiten, zodat voor elke lokale gebruiker een reeks van de aanpassing van de Facebook-gebruikers krijgen we. Vervolgens zullen we gebruiken DefaultIfEmptyom het leven gemakkelijker te maken.
We kunnen houden WithAvatarzoals het was - maar deze keer gaan we alleen maar om het te bellen als we een Facebook-gebruiker aan de avatar uit te pakken hebt. Een groep mee in C # query expressies wordt voorgesteld door join ... into. Deze vraag is redelijk lang, maar het is niet te eng, eerlijk!
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);
}
Hier is de query-expressie weer, maar met opmerkingen:
// "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);
De niet-LINQ oplossing
Een andere optie is om alleen in zeer geringe mate gebruik maken van LINQ. Bijvoorbeeld:
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;
}
}
}
Dit maakt gebruik van een repeater blok in plaats van een LINQ query-expressie. ToDictionaryzal een uitzondering te gooien als het dezelfde toets twee keer ontvangt - een optie om te werken rond dit is om te veranderen GetFacebookUsersom ervoor te zorgen dat het kijkt alleen voor verschillende ID's:
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
}
Dat veronderstelt de webservice werkt naar behoren, natuurlijk - maar als het niet gebeurt, wilt u waarschijnlijk een uitzondering toch te gooien :)
Conclusie
Maak uw keuze uit de drie. De groep aan te sluiten is waarschijnlijk het moeilijkst te begrijpen, maar gedraagt zich best. De iterator blok oplossing is misschien wel de eenvoudigste en moeten gedragen goed met de GetFacebookUserswijziging.
Het maken van Useronveranderlijke zou vrijwel zeker een positieve stap wel.
Een leuke bijproduct van al deze oplossingen is dat de gebruikers uit te komen in dezelfde volgorde waarin ze in gingen. Dat is misschien ook niet belangrijk voor je zijn, maar het kan een mooie woning.
Hoop dat dit helpt - het is een interessante vraag :)
EDIT: Is mutatie de weg te gaan?
Na het zien in je reactie dat de lokale gebruiker type is eigenlijk een soort entiteit van de entiteit kader is het misschien niet gepast om deze gang van zaken te nemen. Waardoor het onveranderlijk is vrij veel uit van de vraag, en ik vermoed dat de meeste toepassingen van het type zal verwachten mutatie.
Als dat het geval is, kan het de moeite waard het veranderen van de interface te maken dat duidelijker. In plaats van terug te keren een IEnumerable<User>(wat impliceert - tot op zekere hoogte - projectie) je zou willen zowel de ondertekening en de naam te wijzigen, zodat u met iets als dit:
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
}
}
Nogmaals, dit is niet een bijzonder "LINQ-y"-oplossing (in de belangrijkste operatie) niet meer - maar dat is redelijk, als je niet echt "bevraging"; je bent "vernieuwing".