Хорошо, это не совсем ясно , что 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полностью неизменны - существуют различные стратегии вокруг , что, например, шаблон строитель. Спросите меня , если вы хотите больше деталей. В любом случае, главное , что мы создали новый пользователь , который является копией старого, но с указанным аватаре.
Первая попытка: внутреннее соединение
Теперь вернемся к вашему картографа ... вы в настоящее время есть три государственные методы , но мое предположение , что только первые один должен быть публичным, а остальная часть API фактически не нужно подвергать пользователей Facebook. Похоже , ваш GetFacebookUsersметод в основном хорошо, хотя я бы , вероятно , выстраиваться запрос с точки зрения пробелов.
Таким образом, учитывая последовательность локальных пользователей и набор пользователей Facebook, мы оставили делать посотреть немного. Прямой «присоединиться» положение является проблематичным, так как он не даст локальных пользователей, которые не имеют соответствия пользователя Facebook. Вместо этого нам нужно каким-то образом лечения пользователя, не Facebook, как если бы они были пользователь Facebook без аватара. По существу, это нулевая модель объекта.
Мы можем сделать это, придумывая пользователь 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. Если это не так, то мы должны были бы вернуться к этому и не использовать внутреннее соединение.
Мы включаем «нулевой» пользователь в конце, затем сделать присоединиться и проект с использованием 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 , чтобы захватить аватар из. Группа присоединиться к выражениям # запроса 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чтобы убедиться , что она выглядит только для различных идентификаторов:
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неизменны почти наверняка будет положительным шагом , хотя.
Один хороших побочным продуктом всех этих решений является то, что пользователи выходят в том же порядке, как они вошли. Это может быть и не важно для вас, но это может быть хорошим свойством.
Надеюсь, что это помогает - это был интересный вопрос :)
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-й» решение (в основном режиме) больше - но это разумно, так как вы на самом деле не «Запросы»; вы «обновление».