Linqを使用してfacebookプロファイルをユーザー情報にマップする

StackOverflow https://stackoverflow.com/questions/603650

  •  03-07-2019
  •  | 
  •  

質問

LINQに関する本を読んだ後、LINQを使用するためにc#で記述したマッパークラスを書き直すことを考えています。誰かが私に手を貸してくれないかと思っています。注:少しわかりにくいですが、ユーザーオブジェクトはローカルユーザーであり、ユーザー(小文字)はFacebook XSDから生成されたオブジェクトです。

オリジナルマッパー

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;
    }
}

最初の2回の試みが壁にぶつかった

試行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);
}
役に立ちましたか?

解決

OK私はそれについて考えたのとほぼ同じようにこれを書きました-それはあなたが次回同じことをするのをより簡単にするので、行動中の思考の流れを見るのに役立つと思います(もちろん、私のソリューションが好きだと仮定して:)

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 を完全に不変にしたい場合もあります。ビルダーパターンなど、さまざまな戦略があります。詳細が必要な場合は私に尋ねてください。とにかく、主なことは、古いユーザーのコピーであるが指定されたアバターを持つ新しいユーザーを作成したことです。

最初の試行:内部結合

マッパーに戻ります...現在3つの public メソッドがありますが、私の guess は最初のメソッドのみがパブリックである必要があり、 APIの残りは、実際にはFacebookユーザーを公開する必要はありません。 GetFacebookUsers メソッドは基本的に問題ないように見えますが、おそらく空白の観点からクエリを並べます。

したがって、ローカルユーザーのシーケンスとFacebookユーザーのコレクションが与えられた場合、実際のマッピングビットはそのままにします。まっすぐな「結合」句は問題があります。これは、一致するFacebookユーザーを持たないローカルユーザーを生成しないためです。代わりに、非FacebookユーザーをアバターのないFacebookユーザーであるかのように扱う何らかの方法が必要です。基本的に、これはnullオブジェクトパターンです。

これを行うには、null 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。そうでない場合は、これを再検討し、内部結合を使用しないでください。

「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ユーザーが重複していると問題が発生します。各ローカルユーザーが2回出てくることになります!
  • これにより、Facebook以外のユーザーのアバターが置き換えられます(削除されます)。

2番目のアプローチ:グループ参加

これらのポイントに対処できるかどうか見てみましょう。 1つの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ソリューション

他のヒント

代わりにこのようなものを書きたいと思います:

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 ループのような Select を使用して、セットの要素に対して実際の変更アクションを実行することはお勧めしません。あなたはそれを行うことができますが、人々はあなたのコードに驚くでしょう、そしてあなたはいつも怠wholeな評価を心に留めておく必要があります。)

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top