Okay, es ist nicht klar , genau das, was IMapperin ihm hat, aber ich würde ein paar Dinge vorschlagen, von denen einige aufgrund anderer Einschränkungen nicht durchführbar sein. Ich habe das ziemlich viel geschrieben, wie ich darüber nachgedacht habe - ich glaube , es hilft , den Gedankengang in Aktion zu sehen, wie es einfacher zu machen , wird für Sie die gleiche Sache beim nächsten Mal zu tun. (Vorausgesetzt , dass Sie meine Lösungen mögen, natürlich :)
LINQ ist von Natur aus funktionalem Stil. Das bedeutet, dass im Idealfall Abfrage sollte keine Nebenwirkungen hat. Zum Beispiel würde ich eine Methode mit einer Signatur von erwarten:
public IEnumerable<User> MapFrom(IEnumerable<User> users)
eine neue Folge von Benutzerobjekten mit zusätzlichen Informationen zurückzukehren, anstatt die bestehenden Benutzer mutiert. Die einzige Information , Sie gerade anhängen ist der Avatar, also würde ich eine Methode in hinzufügen Userentlang der Linien von:
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;
}
Sie könnten sogar machen wollen Uservöllig unveränderlich - gibt es verschiedene Strategien um , dass, wie die Erbauer. Fragen Sie mich , wenn Sie weitere Informationen wünschen. Wie auch immer, die Hauptsache ist , dass wir einen neuen Benutzer erstellt haben , die eine Kopie der alten, aber mit dem angegebenen Avatar.
Erster Versuch: INNER JOIN
Nun zurück zu Ihrem Mapper ... Sie haben derzeit drei bekommen öffentliche Methoden , aber meine Vermutung ist , dass nur die erste zu sein , die Öffentlichkeit braucht, und dass der Rest der API muss nicht tatsächlich die Facebook - Nutzer verfügbar machen. Es sieht aus wie Ihre GetFacebookUsersMethode grundsätzlich in Ordnung, obwohl ich wahrscheinlich die Abfrage in Bezug auf die Leerzeichen in einer Reihe aufstellen würde.
Also, da eine Folge von lokalen Benutzern und eine Sammlung von Facebook-Nutzer, wir links den eigentlichen Mapping Bit zu tun. Eine gerade „Join“ Klausel ist problematisch, weil es nicht die lokalen Benutzer ergeben, die keine passenden Facebook-Nutzer haben. Stattdessen brauchen wir eine Möglichkeit, einen Nicht-Facebook-Nutzer zu behandeln, als ob sie ein Facebook-Nutzer ohne einen Avatar waren. Im Wesentlichen ist dies das Null-Objekt-Muster.
Wir können das tun, indem sie mit einem Facebook-Benutzer, der eine Null-uid hat kommen (das Objektmodell unter der Annahme erlaubt, dass):
// Adjust for however the user should actually be constructed.
private static readonly FacebookUser NullFacebookUser = new FacebookUser(null);
Wir wollen aber eigentlich eine Folge dieser Nutzer, denn das ist , was Enumerable.Concatverwendet:
private static readonly IEnumerable<FacebookUser> NullFacebookUsers =
Enumerable.Repeat(new FacebookUser(null), 1);
Jetzt können wir einfach „add“ diese Dummy - Eintrag in unseren realen und tun eine normale innere Verknüpfung. Beachten Sie, dass dies geht davon aus, dass die Suche von Facebook - Nutzer wird immer einen Benutzer für jede „echte“ Facebook UID finden. Wenn das nicht der Fall ist, müssten wir dies nochmals zu besuchen und nicht verwenden eine innere Verknüpfung.
Wir schließen die „Null“ Benutzer am Ende, hat dann die Verbindung und Projekt mit 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);
}
So ist die volle Klasse wä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
}
}
Ein paar Punkte hier:
- Wie bereits erwähnt, kommt die innere problematisch wird, wenn ein Facebook-UID des Benutzers nicht als gültigen Benutzer abgerufen werden kann.
- Ebenso bekommen wir Probleme, wenn wir doppelte Facebook-Nutzer haben - jeder lokale Benutzer würde zweimal herauskommen am Ende!
- Dies ersetzt (entfernt), um den Avatar für Nicht-Facebook-Nutzer.
Ein zweiter Ansatz: Gruppe beitreten
Mal sehen , ob wir diese Punkte ansprechen können. Ich gehe davon aus, dass , wenn wir geholt haben mehrere Facebook - Nutzer für eine einzelne Facebook UID, dann ist es nicht, wer von ihnen egal , greifen wir den Avatar aus - sie sollten gleich sein.
Was wir brauchen , ist eine Gruppe beitreten, so dass für jeden lokalen Benutzer wir eine Sequenz von passenden Facebook - Nutzer erhalten. Verwenden wir dann DefaultIfEmptydas Leben leichter zu machen.
Wir können halten , WithAvatarwie es vorher war - aber diesmal wir es nur gehen zu nennen , wenn wir einen Facebook - Nutzer haben von dem Avatar zu greifen. Eine Gruppe beitreten in C # Abfrage - Ausdrücken dargestellt durch join ... into. Diese Abfrage ist ziemlich lang, aber es ist nicht zu gruselig, ehrlich!
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 ist der Abfrage-Ausdruck wieder, aber mit Kommentaren:
// "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);
Die nicht-LINQ-Lösung
Eine weitere Option ist nur LINQ zu verwenden, sehr leicht. Beispielsweise:
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;
}
}
}
Dieser verwendet einen Iteratorblock anstelle einer LINQ - Abfrage - Ausdruck. ToDictionarywird eine Ausnahme werfen , wenn er zweimal den gleichen Schlüssel erhält - eine Option , um dieses Werk zu ändern ist GetFacebookUserses sieht nur für eindeutigen IDs , um sicherzustellen:
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
}
Das übernimmt der Web-Service funktioniert in geeigneter Weise, natürlich - aber wenn es nicht tut, wollen Sie wahrscheinlich eine Ausnahme sowieso werfen :)
Fazit
Treffen Sie Ihre Wahl aus den dreien. Die Gruppe beizutreten , ist wahrscheinlich am schwersten zu verstehen, sondern verhält sich am besten. Die Iteratorblock Lösung ist vielleicht die einfachste und sollte mit der Ordnung verhalten GetFacebookUsersModifikation.
Etwas herstellen , Userobwohl unveränderlich wäre mit ziemlicher Sicherheit ein positiver Schritt.
Ein schönes Nebenprodukt all diese Lösungen besteht darin, dass der Anwender kommt in der gleichen Reihenfolge, in sie gingen. Das mag für Sie nicht wichtig sein, aber es kann ein schönes Hotel sein.
Hoffe, das hilft - es war eine interessante Frage :)
EDIT: Ist Mutation der Weg zu gehen?
Nachdem in Ihren Kommentaren zu sehen , dass der lokale Benutzertyp tatsächlich ein Entitätstyp aus dem Entity Framework ist, es kann nicht sinnvoll sein , diese Vorgehensweise zu nehmen. Es unveränderlich zu machen ist ziemlich außer Frage, und ich vermute , dass die meisten Anwendungen des Typs erwarten Mutation.
Wenn das der Fall ist, kann es sich lohnen , wenn Sie Ihre Schnittstelle dass klarer zu machen. Statt der Rückkehr ein IEnumerable<User>(was bedeutet , - bis zu einem gewissen Grad - Projektion) Sie sowohl die Unterschrift und den Namen ändern möchten, können Sie mit so etwas wie dieses zu verlassen:
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
}
}
Auch dies ist nicht besonders „LINQ-y“ Lösung (im Hauptbetrieb) nicht mehr - aber das ist sinnvoll, da man nicht wirklich „anfragende“ sind; Sie „Aktualisierung“.