Va bene, non è chiaro esattamente ciò che IMapperha in sé, ma mi piacerebbe suggerire alcune cose, alcuni dei quali non può essere fattibile a causa di altre restrizioni. Ho scritto questo fuori più o meno come ho pensato - Penso che aiuta a vedere la linea di pensiero in azione, come che ti rendono più facile per voi di fare la stessa cosa la prossima volta. (Supponendo che si piacciono le mie soluzioni, ovviamente :)
LINQ è intrinsecamente funzionale in stile. Ciò significa che idealmente, le query non dovrebbero avere effetti collaterali. Per esempio, mi aspetto un metodo con una firma di:
public IEnumerable<User> MapFrom(IEnumerable<User> users)
per restituire una nuova sequenza di oggetti utente con informazioni aggiuntive, piuttosto che mutando gli utenti esistenti. L'unica informazione che si sta aggiungendo è l'avatar, quindi mi piacerebbe aggiungere un metodo a Userlungo le linee di:
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;
}
Si potrebbe anche voler fare Userpienamente immutabili - ci sono varie strategie intorno a quello, come il builder. Chiedimi se volete maggiori dettagli. In ogni caso, la cosa principale è che abbiamo creato un nuovo utente che è una copia di quello vecchio, ma con l'avatar specificato.
Primo tentativo: inner join
Ora di nuovo al vostro mapper ... hai attualmente ottenuto tre pubblici metodi ma la mia ipotesi è che solo il primo deve essere pubblico, e che il resto delle API in realtà non ha bisogno di esporre gli utenti di Facebook. Sembra che il tuo GetFacebookUsersmetodo è sostanzialmente a posto, anche se probabilmente sarei allineare la query in termini di spazio bianco.
Quindi, data una sequenza di utenti locali e una collezione di utenti di Facebook, non ci resta a fare il bit di mappatura reale. Una clausola "join" retta è problematico, perché non produrrà gli utenti locali che non dispongono di un utente di Facebook corrispondenti. Invece, abbiamo bisogno di qualche modo di trattare un utente non-Facebook, come se fossero un utente di Facebook, senza un avatar. Essenzialmente questo è il modello oggetto nullo.
Possiamo farlo da venire con un utente di Facebook che ha un uid null (supponendo che il modello a oggetti permette che):
// Adjust for however the user should actually be constructed.
private static readonly FacebookUser NullFacebookUser = new FacebookUser(null);
Tuttavia, vogliamo davvero una sequenza di questi utenti, perché è quello che Enumerable.Concatutilizza:
private static readonly IEnumerable<FacebookUser> NullFacebookUsers =
Enumerable.Repeat(new FacebookUser(null), 1);
Ora possiamo semplicemente "aggiungere" questa voce fittizia per la nostra vera, e ti unisci a una normale interiore. Si noti che questo presuppone che la ricerca degli utenti di Facebook sarà sempre trovare un utente per qualsiasi "vero" Facebook UID. Se questo non è il caso, avremmo bisogno di rivisitare questo e non utilizzare un inner join.
Includiamo l'utente "null", alla fine, poi fare il join e progetto utilizzando 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);
}
Così la classe completa sarebbe:
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
}
}
Alcuni punti qui:
- Come notato in precedenza, il join interno diventa problematico se Facebook UID di un utente potrebbe non essere recuperato come utente valido.
- Allo stesso modo otteniamo problemi se abbiamo gli utenti duplicati Facebook - ogni utente locale finirebbe per uscire due volte!
- Questo sostituisce (rimuove) l'avatar per gli utenti non-Facebook.
Un secondo approccio: gruppo Partecipa
Vediamo se siamo in grado di affrontare questi punti. Darò per scontato che se abbiamo recuperati più utenti di Facebook per un singolo Facebook UID, quindi non importa quale di essi ci afferra l'avatar da - dovrebbero essere gli stessi.
Quello che ci serve è un gruppo di unirsi, in modo che per ogni utente locale si ottiene una sequenza di corrispondenza utenti di Facebook. Provvederemo quindi utilizziamo DefaultIfEmptyper rendere la vita più facile.
Possiamo continuare a WithAvatarcome era prima - ma questa volta stiamo solo andando a chiamarlo se abbiamo un utente di Facebook per afferrare l'avatar da. Un gruppo si uniscono in C # espressioni di query è rappresentato da join ... into. Questa query è ragionevolmente lungo, ma non è troppo spaventoso, onesto!
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);
}
Ecco l'espressione di query di nuovo, ma con i commenti:
// "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);
La soluzione non LINQ
Un'altra opzione è quella di utilizzare solo LINQ leggermente. Per esempio:
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;
}
}
}
Questo utilizza un blocco iteratore invece di un'espressione di query LINQ. ToDictionarysarà un'eccezione se riceve due volte la stessa chiave - una possibilità per risolvere questo è quello di cambiare GetFacebookUsersper assicurarsi che sembra solo per gli ID distinte:
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
}
Che assume il servizio di web funziona in modo appropriato, ovviamente - ma se così non fosse, probabilmente vuole un'eccezione comunque :)
Conclusione
Fate la vostra scelta dei tre. Il gruppo di join è probabilmente più difficile da capire, ma si comporta meglio. La soluzione blocco iteratore è forse il più semplice, e dovrebbe comportarsi bene con la GetFacebookUsersmodifica.
Fare Userimmutabile sarebbe quasi certamente un passo positivo però.
Un bel sottoprodotto di tutte queste soluzioni è che gli utenti escono nello stesso ordine sono andati in. Che non può bene essere importante per voi, ma può essere un bell'albergo.
Spero che questo aiuti - è stato una domanda interessante :)
EDIT: E 'la mutazione la strada da percorrere?
Dopo aver visto nei vostri commenti che il tipo di utente locale è in realtà un tipo di entità da Entity Framework, che potrebbe non essere appropriato per prendere questa linea di condotta. Rendere più immutabile è praticamente fuori questione, e ho il sospetto che la maggior parte degli usi di tipo si aspettano mutazione.
Se questo è il caso, può essere utile cambiare l'interfaccia per far sì che più chiaro. Invece di restituire un IEnumerable<User>(che implica - in qualche misura - proiezione) si potrebbe desiderare di cambiare sia la firma e il nome, lasciando con qualcosa di simile a questo:
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
}
}
Ancora una volta, questa non è una soluzione particolarmente "LINQ-y" (in l'operazione principale) più - ma questo è ragionevole, in quanto non sei realmente "interrogazione"; si sta "l'aggiornamento".