LINQ를 사용하여 사용자 정보로 Facebook 프로필을 매핑합니다.

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

  •  03-07-2019
  •  | 
  •  

문제

LINQ에 관한 책을 읽은 후 나는 C#에서 LINQ를 사용하기 위해 쓴 Mapper 클래스를 다시 작성하는 것에 대해 생각하고 있습니다. 누군가 나에게 손을 줄 수 있는지 궁금합니다. 참고 : 약간 혼란 스럽지만 사용자 객체는 로컬 사용자이며 사용자 (소문자)는 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;
    }
}

나의 첫 두 번의 시도는 벽을 쳤다

시도 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 그 안에 있지만 몇 가지를 제안 할 것입니다. 그 중 일부는 다른 제한으로 인해 실현할 수 없을 수 있습니다. 나는 내가 생각한대로 이것을 거의 썼다. 나는 그것이 다음에 같은 일을하기가 더 쉬워 질 것이기 때문에 생각의 기차를 실제로 보는 데 도움이된다고 생각한다. (물론 내 솔루션을 좋아한다고 가정합니다. :)

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 완전히 불변 - 건축업자 패턴과 같은 다양한 전략이 있습니다. 자세한 내용을 원하는지 물어보십시오. 어쨌든, 가장 중요한 것은 우리가 오래된 사용자의 사본이지만 지정된 아바타와 함께 새로운 사용자를 만들었다는 것입니다.

첫 번째 시도 : 내부 조인

이제 Mapper로 돌아가 ... 당신은 현재 3 개를 가지고 있습니다. 공공의 방법이지만 내 추측 첫 번째는 공개되어야하며 나머지 API는 실제로 Facebook 사용자를 노출시킬 필요가 없습니다. 당신처럼 보입니다 GetFacebookUsers 메소드는 기본적으로 괜찮지 만, 아마도 쿼리를 줄무늬로 정렬 할 수 있습니다.

따라서 일련의 로컬 사용자와 Facebook 사용자 모음이 주어지면 실제 매핑 비트를 수행합니다. 직선 "조인"절은 문제가됩니다. 페이스 북 사용자가 일치하지 않는 로컬 사용자를 생성하지 않기 때문입니다. 대신, 우리는 비 페이스 북 사용자를 아바타가없는 Facebook 사용자 인 것처럼 치료하는 방법이 필요합니다. 본질적으로 이것은 널 객체 패턴입니다.

우리는 null uid를 가진 Facebook 사용자를 생각해 낼 수 있습니다 (객체 모델이 허용한다고 가정).

// Adjust for however the user should actually be constructed.
private static readonly FacebookUser NullFacebookUser = new FacebookUser(null);

그러나 우리는 실제로 a를 원합니다 순서 이 사용자들 중 그 이유는 무엇이기 때문입니다 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 UID에 대한 아바타를 얻는 것이 중요하지 않습니다. 동일해야합니다.

우리가 필요로하는 것은 그룹 가입이므로 각 로컬 사용자마다 일련의 페이스 북 사용자를 얻을 수 있습니다. 그런 다음 사용할 것입니다 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
    }

그것은 물론 웹 서비스가 적절하게 작동한다고 가정하지만 그렇지 않다면 어쨌든 예외를 던지고 싶을 것입니다 :)

결론

세 가지 중에서 선택하십시오. 그룹 가입은 아마도 이해하기 가장 어렵지만 가장 잘 행동합니다. 반복자 블록 솔루션은 아마도 가장 간단 할 수 있으며 GetFacebookUsers 가감.

만들기 User 그러나 불변은 거의 확실히 긍정적 인 단계 일 것입니다.

이 모든 솔루션의 좋은 부산물 한 가지는 사용자가 동일한 순서로 나온다는 것입니다. 그것은 당신에게 중요하지 않을 수 있지만 좋은 속성이 될 수 있습니다.

이것이 도움이되기를 바랍니다 - 흥미로운 질문이었습니다 :)

편집 : 돌연변이가가는 길입니까?

귀하의 의견에서 로컬 사용자 유형이 실제로 엔티티 프레임 워크의 엔터티 유형이라는 것을 보았습니다. 5월 이 행동 과정을 수강하는 데 적합하지 않습니다. 불변으로 만드는 것은 의문의 여지가 거의 없으며, 유형의 대부분의 사용이 예상하다 돌연변이.

이 경우 인터페이스를 변경하여 더 명확하게 만들 가치가있을 수 있습니다. 반환하는 대신 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"솔루션 (메인 작업에서)이 더 이상 아닙니다. 그러나 실제로 "쿼리"가 아니기 때문에 합리적입니다. 당신은 "업데이트 중입니다".

다른 팁

나는 대신 이런 것을 쓰는 경향이있다 :

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 사용자 컬렉션이 매우 크지 않으면 눈에 띄는 성능 차이가 발생할 수 있습니다.)

(사용하지 않는 것이 좋습니다 Select 같은 foreach 루프는 세트의 요소, 리팩토링 시도에서 수행 한 방식에 대한 실제 돌연변이 동작을 수행합니다. 당신은 그것을 할 수 있지만, 사람들은 당신의 코드에 놀랄 것이며, 당신은 전체 시간을 명심해야합니다.)

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top