在读一本书上的皇宫我想再写一映射器类,我在写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 循环一样使用选择来对集合的元素执行实际的变异操作,就像你在重构尝试中所做的那样你可以做到这一点,但是人们会对你的代码感到惊讶,并且你必须始终记住懒惰的评估。)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top