#c# #linq
#c# #linq
Вопрос:
Вот что я сделал для Select
пользователей в своей модели, а затем удалил все null
записи:
model.Users = users
.Select(u =>
{
var membershipUser = Membership.GetUser(u.UserName);
return membershipUser != null
? new UserBriefModel
{
Username = u.UserName,
Fullname = u.FullName,
Email = membershipUser.Email,
Roles = u.UserName.GetRoles()
}
: null;
})
.Where(u => u != null)
.ToList();
Интересно, есть ли способ объединить SELECT
WHERE
предложение and .
Я пытался:
model.Users = users
.Select(u =>
{
var membershipUser = Membership.GetUser(u.UserName);
if (membershipUser != null)
return new UserBriefModel
{
Username = u.UserName,
Fullname = u.FullName,
Email = membershipUser.Email,
Roles = u.UserName.GetRoles()
};
})
.ToList();
Но intellisense предлагает синтаксическую ошибку. Что заставляет меня добавить return null
оператор:
model.Users = users
.Select(u =>
{
var membershipUser = Membership.GetUser(u.UserName);
if (membershipUser != null)
return new UserBriefModel
{
Username = u.UserName,
Fullname = u.FullName,
Email = membershipUser.Email,
Roles = u.UserName.GetRoles()
};
return null;
})
.ToList();
Итак, как правильно написать это SELECT
утверждение, чтобы в моей модели были выбраны только допустимые записи?
Комментарии:
1. Какую ошибку сборки вы получаете? Также я бы посоветовал выполнить проверку null, прежде чем пытаться использовать объект, иначе вы получите нулевые ссылки внутри вашего linq, что затрудняет диагностику
2. Почему? В чем проблема с отделением where от select ?
3. Просто хочу знать, возможно ли объединить их. Сначала заполнять модель некоторыми бесполезными записями, а затем отфильтровывать их, кажется немного некрасивым. Не лучше ли было бы отказаться
null
от s вSELECT
предложении?4. Во втором примере кода ваша лямбда неверна — что она возвращает, когда членство равно нулю? Лямбда, анонимные методы — это те же методы — они должны быть правильными методами для использования.
5. Иногда оператор foreach более чистый, чем LINQ. Я думаю, что это может быть одним из таких случаев.
Ответ №1:
Концептуально здесь фактически есть три операции:
- спроецируйте имя пользователя на пользователя-участника
- отфильтровывать пользователей с нулевым членством
- спроецируйте пользователей-членов на модель
Именно так должен выглядеть ваш запрос. Ваш самый первый запрос уже пытался объединить шаги 1 и 3 вместе, но вы испытываете трудности, потому что второй шаг действительно должен быть посередине двух, и препятствия, которые вам нужно преодолеть, чтобы обойти, не очень хороши.
Запрос на самом деле становится более простым и читаемым (и становится идиоматическим кодом LINQ), когда вы представляете все три операции по отдельности.
model.Users = users
.Select(user => new
{
user,
membershipUser = Membership.GetUser(user.UserName)
})
.Where(pair => pair.membershipUser != null)
.Select(pair => new UserBriefModel
{
Username = pair.user.UserName,
Fullname = pair.user.FullName,
Email = pair.membershipUser.Email,
Roles = pair.user.UserName.GetRoles()
})
.ToList();
Это запрос, который также может быть более эффективно написан в синтаксисе запроса:
model.Users = from user in users
let membershipUser = Membership.GetUser(user.UserName)
where membershipUser != null
select new UserBriefModel
{
Username = user.UserName,
Fullname = user.FullName,
Email = membershipUser.Email,
Roles = user.UserName.GetRoles()
};
Что касается буквального вопроса о том, можете ли вы объединить проецирование и фильтрацию в одну операцию LINQ, это, безусловно, возможно. Это было бы неподходящим решением проблемы, но использование SelectMany
может позволить вам фильтровать и проецировать одновременно. Это можно сделать, проецируя элемент либо на последовательность из одного элемента, содержащую значение, на которое вы хотите его спроецировать, либо на пустую последовательность, основанную на предикате.
model.Users = users
.SelectMany(u =>
{
var membershipUser = Membership.GetUser(u.UserName);
return membershipUser != null
? new[]{ new UserBriefModel
{
Username = u.UserName,
Fullname = u.FullName,
Email = membershipUser.Email,
Roles = u.UserName.GetRoles()
}}
: Enumerable.Empty<UserBriefModel>();
}).ToList();
Конечно, каждый раз, когда вы используете этот код, котенок погибает. Не убивайте котят; вместо этого используйте предыдущий запрос.
Комментарии:
1. Почему я получил -2, а парень, который скопировал мой пост, получил 3.
2. @LIUFA Ну, я даже не видел ваш пост, пока вы не упомянули об этом, и не голосовал за него, так что я могу только догадываться, но несколько вещей выделяются. Во-первых, у вас был довольно заметно отличающийся ответ, когда вы впервые опубликовали и отредактировали его позже. Я не уверен, когда поступили голоса, но они, возможно, были основаны на вашей первой редакции. Далее, и что более важно, ваш ответ ничего не объясняет . Просто публикация некоторого кода без объяснения того, что вы изменили и почему вы его изменили, радикально влияет на ценность публикации. Наконец, код в моем ответе не совпадает с вашим, хотя и похож.
3. Я только что потратил около 20 минут, выясняя, как это сделать, только
SelectMany
для того, чтобы исправить мой ответ, и вы все равно победили меня… Черт бы тебя побрал, Серви4. @KyleGobel Теперь вы можете попробовать вызов Эрика и реализовать (неэффективный)
Join
с точки зренияSelectMany
.
Ответ №2:
Я не думаю, что это возможно, Select
сопоставлю все 1-1, насколько я знаю … если вы пытаетесь отфильтровать, вам понадобится Where
.
редактировать редактировать: я больше не верю SelectMany
, что могу это сделать (как показал Серви).
Комментарии:
1. Тогда вы совершенно уверены, что считаете неправильно.
SelectMany
обычно позволяет сглаживать списки списков и приводит к большему количеству элементов, а не к меньшему. Это select, но никоим образом не фильтр.2. @Blaise ничего не может сказать
SelectMany
, но я почти уверен, что первая часть ответа верна. К сожалению, я не думаю, что вы сможете втиснуть их в 1 метод.3. @TzahMama:
SelectMany
это невероятно полезный метод, который позволяет превратить древовидную структуру в список для работы: вы могли бы сделать что-то вродеusers.SelectMany(user => user.UserName.GetRoles())
, и результатом будет список всех ролей, которые есть у любого пользователя, вероятно, с дубликатами. Это ценно, просто здесь неуместно.4. Мой плохой, я был более или менее под впечатлением, что
SelectMany
мог бы сделать что угодно с помощью небольшого волшебства и хитрости5. @KyleGobel: Но теперь, в результате, вы сможете использовать его правильно! Этот ответ привел к чистому получению знаний.
Ответ №3:
Я не знаю ни о каком методе Linq, который позволит вам произвольно добавлять или не добавлять значение в результирующий IEnumerable.
Для этого лямбда (селектор, предикат, фильтр …) должен иметь возможность управлять этим добавлением. И только предикаты (Where ) могут это сделать. В вашем случае вам нужно будет выполнить предикат (Where) и выбрать. Не существует комбинационного метода, который будет выполнять оба для вас одновременно, за исключением одного непрямого метода, описанного в конце ответа.
model.Users = users
.Where(u => Membership.GetUser(u.UserName) != null)
.Select(u =>
{
return new UserBriefModel
{
Username = u.UserName,
Fullname = u.FullName,
Email = Membership.GetUser(u.UserName).Email,
Roles = u.UserName.GetRoles()
};
})
.ToList();
Мы либо получаем два Membership.getUser(u.UserName) с такой предварительной фильтрацией, либо заканчиваем вашей первоначальной постфильтрацией.
Это просто меняет сложность. И трудно сказать, где производительность будет лучше. Это зависит от того, является ли Membership.GetUser
это быстрым, и есть много пользователей, не являющихся членами — для моего примера. Или, если Membership.GetUser
это требует много ресурсов и мало пользователей, не являющихся членами, ваш пример с postfilter лучше.
Как и любое решение, основанное на производительности, оно должно быть тщательно рассмотрено и проверено. В большинстве случаев разница минимальна.
Как уже было показано в другом сообщении и указано г-ном ‘Servy’, это можно сделать, используя один вызов SelectMany
SelectMany, выбирая либо пустой IEnumerable, либо 1-элементный массив. Но я по-прежнему считаю первое утверждение технически правильным, потому что SelectMany возвращает коллекцию элементов (он точно не добавляет или не добавляет один элемент напрямую):
model.Users = users
.SelectMany(u =>
{
var membership = Membership.GetUser(u.UserName);
if (membership == null)
return Enumerable.Empty<UserBriefModel>();
return new UserBriefModel[]
{
new UserBriefModel()
{
Username = u.UserName,
Fullname = u.FullName,
Email = membership.Email,
Roles = u.UserName.GetRoles()
}
};
})
.ToList();
Комментарии:
1. MembershipUser = Membership.getUser(u.UserName); если (MembershipUser != null) … превращается в . Где(u => Membership.getUser(u.UserName) != null). Я не вижу никакой ошибки. Извините, я ошибаюсь
2. Немного дублирования, но я не думаю, что там можно многое сделать. Мне все еще нравится, как это выглядит лучше, чем OP.
3. Да, LINQ выглядит лучше без условных выражений. Но дело не в этом.
4. Вопрос в том, можно ли объединить select и where, что рассматривается здесь. Этот ответ является правильным ответом.
5. @Magus Но их можно объединить. Неверный ответ указывает, что они не могут быть. Теперь их не следует объединять. Это было бы очень плохой практикой, но это абсолютно возможно.
Ответ №4:
Для этого можно использовать один метод:
private IEnumerable<UserBriefModel> SelectUserBriefModels(IEnumerable<User> users)
{
foreach (var user in users)
{
var membershipUser = Membership.GetUser(user.UserName);
if (membershipUser != null)
{
yield return new UserBriefModel
{
Username = user.UserName,
Fullname = user.FullName,
Email = membershipUser.Email,
Roles = user.UserName.GetRoles()
};
}
}
}
Вы бы использовали его так:
model.Users = SelectUserBriefModels(users);
Ответ №5:
model.Users = users
.Where(u => u.Membership != null)
.Select(u => new UserBriefModel
{
Username = u.UserName,
Fullname = u.FullName,
Email = u.Membership.Email,
Roles = u.UserName.GetRoles()
})
.ToList();
Сначала фильтруйте, затем выберите. Для этого решения вам необходимо иметь свойство навигации, чтобы вы могли делать u.Membership.Email
вместо membershipUser.Email
.
Мои пользователи выглядят примерно так:
public class UserProfile
{
// other properties
public virtual Membership Membership { get; set; }
}
где Membership
находится объект, представляющий таблицу членства и отображаемый через:
modelBuilder.Entity<Membership>()
.HasRequired<UserProfile>(m => m.User)
.WithOptional(u => u.Membership);
Затем вы можете выбрать все одним запросом. Некоторые другие решения здесь также работают нормально, но каждый вызов Membership.GetUser(u.UserName)
приводит к одному дополнительному вызову DB.
Комментарии:
1. Ваш код выбирает что-то совершенно другое. Куда это делается
Membership.GetUser
?2. @KlausByskovPedersen прав. Я проверяю
Membership.GetUser(u.UserName)
.3. @KlausByskovPedersen: Теперь я изменил его — таким образом, он работает / работал в моем коде.
4. @chrfin Это не проблема OP
5. @TzahMama: это рабочее решение проблемы OP (выберите a
UserBriefModel
для каждого пользователя, у которого есть учетная запись участника) с дополнительным преимуществом гораздо меньшего количества запросов к БД — я использую это в своем приложении таким образом…