LINQ를 사용하여 사용자 정보로 Facebook 프로필을 매핑합니다.
문제
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
루프는 세트의 요소, 리팩토링 시도에서 수행 한 방식에 대한 실제 돌연변이 동작을 수행합니다. 당신은 그것을 할 수 있지만, 사람들은 당신의 코드에 놀랄 것이며, 당신은 전체 시간을 명심해야합니다.)