题
在读一本书上的皇宫我想再写一映射器类,我在写c#使用皇宫.我想知道如果有人能给我一个手。注:它有点令人困惑,但用户对象是当地的用户和用户(大写)为对象产生的Facebook划.
原映射器
public class FacebookMapper : IMapper
{
public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
var facebookUsers = GetFacebookUsers(users);
return MergeUsers(users, facebookUsers);
}
public 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
}
public IEnumerable<User> MergeUsers(IEnumerable<User> users, Facebook.user[] facebookUsers)
{
foreach(var u in users)
{
var fbUser = facebookUsers.FirstOrDefault(f => f.uid == u.FacebookUid);
if (fbUser != null)
u.FacebookAvatar = fbUser.pic_sqare;
}
return users;
}
}
我的第一次两次击中墙壁
试图1
public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
// didn't have a way to check if u.FacebookUid == null
return from u in users
join f in GetFacebookUsers(users) on u.FacebookUid equals f.uid
select AppendAvatar(u, f);
}
public void AppendAvatar(User u, Facebook.user f)
{
if (f == null)
return u;
u.FacebookAvatar = f.pic_square;
return u;
}
试图2
public IEnumerable<User> MapFrom(IEnumerable<User> users)
{
// had to get the user from the facebook service for each single user,
// would rather use a single http request.
return from u in users
let f = GetFacebookUser(user.FacebookUid)
select AppendAvatar(u, f);
}
解决方案
好吧,这是不清楚到底是什么 IMapper
有,但我想建议一些东西,其中有些可能是不可行的,由于其他限制。我写的这一出相当多,因为我想过这个-我认为这有助于看到了火车的思想的行动,因为那将使它更容易为你做同样的事情,接下来的时间。(假设你喜欢我的解决方案,当然:)
皇宫本质上是功能性的风格。这意味着,理想的是,查询不应有的副作用。例如,我期望的一个方法有一个签名:
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的用户不一的化身。从本质上讲,这是空对象的图案。
我们可以通过带来一个Facebook用户有一个空uid(假设的对象模型允许):
// 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。如果不是这种情况下,我们需要重新审视这并不是使用内部加入。
我们包括"null"用户结束时,那么在加入和项目使用 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用户为一个单一的Facebook UID,然后它没关系他们的我们抓住的头像从他们应该相同。
我们需要的是一个集团加入,以便每个当地用户,我们得到一个序列的匹配的Facebook用户。然后,我们将使用 DefaultIfEmpty
来让生活更容易。
我们可以继续 WithAvatar
因为它是以前-但这次我们只是要叫它,如果我们已经有了一个Facebook用户抓住的化身。A组加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);
非皇宫解决方案
另一个选择是只使用皇宫很轻微。例如:
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;
}
}
}
这种使用的迭代方框,而不是皇宫查询的表达。 ToDictionary
将把一个异常,如果它收到相同的密钥两次-一个选项工作围绕这是为了改变 GetFacebookUsers
以确保它只寻找不同的标识:
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
}
那假设的网服务的工作适当的,当然-但是如果它不,你可能想把一个异常的反正:)
结论
把你挑选出的三个。本集团参加可能是最难以理解的,但是表现最好。迭代的框解决方案可能是最简单的,应该表现得好的 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
}
}
再一次,这不是一个特别的"皇宫-y"解决方案(在主要的操作)任何更多的-但这是合理的,因为你不是真的"询问";你的"更新".
其他提示
我倾向于写下这样的东西:
public class FacebookMapper : IMapper
{
public IEnumerable<User> MapFacebookAvatars(IEnumerable<User> users)
{
var usersByID =
users.Where(u => u.FacebookUid.HasValue)
.ToDictionary(u => u.FacebookUid.Value);
var facebookUsersByID =
GetFacebookUsers(usersByID.Keys).ToDictionary(f => f.uid);
foreach(var id in usersByID.Keys.Intersect(facebookUsersByID.Keys))
usersByID[id].FacebookAvatar = facebookUsersByID[id].pic_sqare;
return users;
}
public Facebook.user[] GetFacebookUsers(IEnumerable<int> uids)
{
// return facebook users for uids using WCF
}
}
但是,我不会声称这比你所获得的有了很大改进(除非用户或facebook用户集合非常大,在这种情况下你最终可能会有明显的性能差异。)
(我建议不要像 foreach
循环一样使用选择
来对集合的元素执行实际的变异操作,就像你在重构尝试中所做的那样你可以做到这一点,但是人们会对你的代码感到惊讶,并且你必须始终记住懒惰的评估。)