2009-03-02 2 views
3

LINQ에 관한 책을 읽은 후, LINQ를 사용하기 위해 C#에서 쓴 mapper 클래스를 다시 작성하려고합니다. 나는 누군가가 나에게 손을 줄 수 있는지 궁금해. 참고 : 다소 혼란 스럽지만 User 객체는 로컬 사용자이며 사용자 (소문자)는 Facebook XSD에서 생성 된 객체입니다.Linq를 사용하여 내 사용자 정보로 페이스 북 프로필 매핑하기

원래 매퍼

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); 
} 
내가 대신 같은 것을 작성하는 경향 것

답변

9

좋아요, 정확히 IMapper에 무엇이 있는지 확실하지 않지만 몇 가지를 제안하고, 그 중 일부는 다른 제한 사항으로 인해 실현 불가능할 수도 있습니다. 나는 이것을 생각해 보았을 때 거의 이것을 썼다. 나는 다음에 똑같은 일을 더 쉽게 할 수 있도록 행동의 생각의 기차를 보는 ​​것이 도움이된다고 생각한다. (물론 내 솔루션을 좋아한다고 가정하십시오.

LINQ는 본질적으로 스타일로 작동합니다. 이는 이상적으로 검색어에 부작용이 없어야 함을 의미합니다. 예를 들어, 나는의 서명하는 방법을 기대할 :

public IEnumerable<User> MapFrom(IEnumerable<User> users) 

오히려 기존 사용자를 돌연변이보다 추가 정보와 함께 사용자 개체의 새로운 시퀀스를 반환 할 수 있습니다. 현재 추가하고 유일한 정보는 아바타, 그래서의 라인을 따라 User에 메소드를 추가 것 : 심지어 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; 
} 

- 그 주변에 다양한 전략이있다, 빌더 패턴과 같은 자세한 내용을 원하면 저에게 물어보십시오. 어쨌든 중요한 것은 이전 사용자의 복사본 인 새 사용자를 만들었지 만 지정된 아바타가 있어야한다는 것입니다.

첫 번째 시도 : 내부는 당신의 매퍼에 다시 지금

가입 ... 당신은 현재 세 공공 방법을 가지고 있지만 는 첫 번째는 공개 할 필요가 있다고 추측 내, 및 나머지 API는 실제로 Facebook 사용자를 노출 할 필요가 없습니다.GetFacebookUsers 메서드는 기본적으로 괜찮은 것처럼 보입니다.하지만 공백을 고려하여 쿼리를 정렬 할 수는 있습니다.

로컬 사용자의 시퀀스와 Facebook 사용자 모음을 고려하면 실제 매핑 비트가 남습니다. 직선의 "join"절은 일치하는 Facebook 사용자가없는 로컬 사용자를 양보하지 않기 때문에 문제가됩니다. 대신, 우리는 Facebook 사용자가 아바타가없는 것처럼 비 Facebook 사용자를 치료할 방법이 필요합니다. 본질적으로 이것은 null 객체 패턴입니다.

우리는 널 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 사용자가 아닌 사용자의 아바타를 대체 (제거)합니다.

두 번째 방법 : 그룹은 우리가 이러한 점을 해결 할 수 있는지 보자

가입 할 수 있습니다. 우리가 페이 스북 사용자 중 단일 페이스 북의 UID를 가져온 경우, 그들 중 어떤 사람이 우리가 아바타를 차지하는지 상관하지 않는다고 가정합니다. 그들은 동일해야합니다.

우리가 필요로하는 것은 그룹 가입입니다. 따라서 각 로컬 사용자에게 일치하는 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 쿼리 표현식 반복기 블록을 사용한다. 웹 서비스를 가정

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 
    } 

물론, 적절하게 작동합니다 - 그것은 두 번 같은 키를 받으면 ToDictionary이 예외가 발생합니다이 문제를 해결하기위한 하나의 옵션은 확인은 별개의 아이디를 검색 할 GetFacebookUsers을 변경하는 것입니다 - 그렇지 않은 경우에, 당신은 아마 세에서 골라 봐 어쨌든 예외 :

결론

을 던져합니다. 그룹 조인은 아마도 이해하기가 어렵지만 가장 잘 동작합니다. 반복자 블록 솔루션은 아마도 가장 간단 할 수 있으며 GetFacebookUsers 수정으로 정상적으로 동작해야합니다.

User을 불변으로 만드는 것은 거의 확실한 긍정적 인 단계입니다.

이러한 모든 솔루션의 좋은 부산물 중 하나는 사용자가 들어오는 순서와 똑같은 순서로 나와야한다는 것입니다. 이는 사용자에게는 중요하지 않지만 좋은 속성 일 수 있습니다. 가 돌연변이가는 방법 : -

희망하는 데 도움이 그것은 흥미로운 질문 :

EDIT이었다?

로컬 사용자 유형이 실제로 엔티티 프레임 워크에서 개체 유형입니다 귀하의 의견에 보이는 데, 행동이 과정을하기에 적합하지 않을 수 있습니다. 그것을 불변으로 만드는 것은 꽤 많은 의문의 여지가 있습니다. 그리고 저는이 타입의 대부분이 을 기대하고 있습니다. 변이를 기대합니다.

그런 경우 인터페이스를 변경하여 그 값을 명확하게 할 가치가 있습니다. 대신 (- 어느 정도 - 의미있는 돌출부)를 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 "솔루션 (더 이상 주요 운영에 포함되지 않음)이 더 이상 필요 없습니다. 당신은 "업데이트 중"입니다.

+0

Jon, This is awesome +1, 너의 일이 너무 좋아. 그룹화를 이해한다고 생각합니다. 그 옵션을 사용할 수도 있습니다. 그룹화 메서드를 사용하면 .Concat (NullFacebookUsers)을 호출해야합니까? 또한, 내 로컬 사용자는 EF 개체입니다, 당신은 그 좋은 복제 방법을 알고 있습니까? – bendewey

+0

아니요, 그룹화 비트는 그 중 하나와 일치하지 않는 "비 페이스 북 사용자"를 처리하므로 DefaultIfEmpty()를 사용합니다. First(). 그리고 아니, 나는 두려워하는 EF 엔티티 복제에 대해 모른다. ( –

+0

@bendewey :이 EF 특성을 감안할 때, 내 대답에 추가 비트를 추가했다. ( –

5

그러나

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

, 나는 사용자 또는 페이스 북 사용자 컬렉션 않는 한 그 (당신이있어 무엇에 비해 큰 개선했다 주장 할 것 매우 큰,이 경우 당신은 눈에 띄는 성능 차이와 바람 수 있습니다.)

(I는, 세트의 요소에 실제 돌연변이 작업을 수행하기 위해 foreach 루프처럼 Select을 사용에 대해 당신이 한 방법을 권 해드립니다 귀하의 리펙토링 시도. 할 수는 있지만 사람들은 코드에 놀랄 것이고 평생 게으른 평가를 염두에 두어야 할 것입니다.)

+0

+1 LINQ의 Jon 그룹화 기술이 도움이되었다고 대답 해 주셔서 감사합니다. – bendewey

관련 문제