好吧,現在還不清楚IMapper
究竟有什麼,但我會建議一些事情,其中一些可能由於其他限制而不可行。我已經寫了很多,因爲我已經考慮過了 - 我認爲這有助於看到行動中的思路,因爲這會讓您下次更容易做同樣的事情。 (假設你喜歡我的解決方案,當然:)
LINQ本質上是風格的功能。這意味着理想情況下,查詢不應該有副作用。舉例來說,我期望的方法用的簽名:
public IEnumerable<User> MapFrom(IEnumerable<User> users)
返回用戶對象的一個新的序列有額外的信息,而不是突變的現有用戶。您目前追加的唯一信息是動漫形象,所以我會在User
沿線的添加方法:
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;
}
你甚至可能想使User
完全不可改變的 - 有周圍的各種策略,如生成器模式。詢問我是否需要更多詳細信息。無論如何,最主要的是我們已經創建了一個新用戶,它是舊的用戶的副本,但具有指定的頭像。
第一次嘗試:內部連接
現在回到你映射......你現在有三個公共方法,但我猜是只有第一個必須是公開,其餘的API實際上並不需要公開Facebook用戶。它看起來像你的GetFacebookUsers
方法基本上沒問題,雖然我可能根據空白排列查詢。
因此,給定一系列本地用戶和一系列Facebook用戶,我們只剩下實際的映射位。直接的「加入」條款是有問題的,因爲它不會產生沒有匹配Facebook用戶的本地用戶。相反,我們需要一種將非Facebook用戶視爲沒有虛擬形象的Facebook用戶的方式。基本上這是空對象模式。
我們可以做到這一點想出誰擁有空UID Facebook的用戶(假設對象模型允許):
// Adjust for however the user should actually be constructed.
private static readonly FacebookUser NullFacebookUser = new FacebookUser(null);
然而,我們實際上需要一個序列這些用戶的,因爲這就是Enumerable.Concat
用途:
private static readonly IEnumerable<FacebookUser> NullFacebookUsers =
Enumerable.Repeat(new FacebookUser(null), 1);
現在,我們可以簡單地「增加」這個虛擬記錄到我們真正的一個,做一個正常的內連接。請注意,這個假設,Facebook用戶的查找將總是找到任何「真正的」Facebook UID的用戶。如果情況並非如此,我們需要重新考慮這一點,而不是使用內部連接。
我們在末尾加「空」的用戶,然後再做連接和使用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);
}
所以滿級是:
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
}
}
幾個點這裏:
- 如前所述,如果用戶的Facebook UID可能不會被提取爲有效用戶。
- 同樣,如果我們有重複的Facebook用戶,每個本地用戶最終會出現兩次!
- 這將替換(刪除)非Facebook用戶的頭像。
第二種方法:組加入
讓我們看看,如果我們可以解決這些問題。我假設,如果我們已經爲單個Facebook UID獲取了多個 Facebook用戶,那麼我們從中獲取哪個頭像並不重要 - 它們應該是相同的。
我們需要的是一個羣組連接,因此對於每個本地用戶,我們都會得到一系列匹配的Facebook用戶。然後我們將使用DefaultIfEmpty
使生活更輕鬆。
我們可以像以前一樣保留WithAvatar
--但是這一次我們只會打電話給我們,如果我們有Facebook用戶從中獲取頭像。在C#查詢表達式中的組加入由join ... into
表示。這個查詢是相當長的,但它不是太可怕,誠實!
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);
}
這裏的查詢表達式一遍,但評論:
// "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);
非LINQ的解決方案
另一種選擇是隻使用LINQ非常輕微。例如:
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;
}
}
}
這使用迭代器塊而不是LINQ查詢表達式。如果收到兩次相同的密鑰ToDictionary
將拋出一個異常 - 一個選項來解決,這是改變GetFacebookUsers
,以確保它只會尋找不同的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
}
這假定Web服務的工作適當,當然 - 但如果沒有,你可能想反正拋出一個異常:)
結論
任你選出來的三個。團隊加入可能最難理解,但表現最好。迭代器塊解決方案可能是最簡單的,並且應該在GetFacebookUsers
修改中表現良好。
製作User
不可變將幾乎肯定會是一個積極的一步,雖然。
所有這些解決方案的一個很好的副產品是用戶以與他們相同的順序出來。這對您來說可能並不重要,但它可能是一個很好的屬性。
希望這有助於 - 這是一個有趣的問題:)
編輯:是否突變的路要走?
在您的評論中看到,本地用戶類型實際上是實體框架中的實體類型,它可能不適合採取此操作。使它不可變是非常不可能的,我懷疑這種類型的大多數用途將會在期望突變。
如果是這種情況,可能需要更改界面以使其更清晰。不是返回IEnumerable<User>
( - 在一定程度上 - 這意味着投影),你可能想改變這兩個簽名和名字,讓你有這樣的事情:
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
}
}
再次,這是不是一個特別「 LINQ-y「解決方案(在主操作中) - 但這是合理的,因爲你並不真正」查詢「;你正在「更新」。
喬恩,這真棒+1,我愛你的工作。我想我理解這個分組,我可能會選擇這個選項。如果我使用分組方法,是否必須調用.Concat(NullFacebookUsers)?另外,我的本地用戶是一個EF對象,你知道一個好的克隆方法嗎? – bendewey 2009-04-01 22:20:36