Reputation: 22222
Here is what I have done to Select
users into my model and then remove all the null
records:
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();
Wondering if there is a way to combine the SELECT
and WHERE
clause.
I tried:
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();
But the intellisense suggest a syntax error. Which forces me to add a return null
statement:
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();
So what is the correct way to write this SELECT
statement so only valid records are selected into my model?
Upvotes: 4
Views: 5821
Reputation: 420
You can use a single method to accomplish this:
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()
};
}
}
}
You would use it like this:
model.Users = SelectUserBriefModels(users);
Upvotes: 0
Reputation: 10401
I don't know about any Linq method which will allow you to arbitrary add or to not add the value into the resulting IEnumerable.
To do it the lambda(selector, predicate, filter...) should be able to control this addition. And only predicates(Where) are able to do it. In your case you will have to execute predicate(Where) and Select. There is no combinational method which will do both for you at the same time, except one non-direct method described in the end of the answer.
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();
We either get two Membership.GetUser(u.UserName) with such prefiltering or we will end with your original postfiltering.
That's just shifting the complexity. And it is difficult to say where the performance will be better.
It depends on whether the Membership.GetUser
is fast and there are a lot of non-membership users - for my example. Or if Membership.GetUser
is resource-consuming and there are few non-membership users your example with postfilter is better.
As any performance based decision it should be thoroughly considered and checked. In most cases the difference is minimal.
As it was already shown in the another post and pointed by Mr. 'Servy' it is possible to do so using one call of SelectMany
SelectMany selecting either empty IEnumerable or 1-element array. But I still consider the first statement to be technically correct, because SelectMany returns collection of elements (it does not exactly add or doesn't add single element directly):
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();
Upvotes: 0
Reputation: 5750
I don't think this is possible, Select
will map everything 1-1 as far as I know...if you're trying to filter you will need a Where
.
edit edit:
I no longer believe SelectMany
can do it (As Servy has shown).
Upvotes: 1
Reputation: 203845
Conceptually you actually have three operations here:
That is how your query should be looking. Your very first query has already tried to combine steps 1 and 3 together, but you're struggling because step two really should be in the middle of the two, and the hoops that you need to jump through to get around that aren't pretty.
The query actually becomes simpler and readable (and becomes idiomatic LINQ code) when you represent all three operations individually.
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();
This is a query that can also be written more effectively in query syntax:
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()
};
As for the literal question of whether or not you can combine the projecting an filtering into a single LINQ operation, it is certainly possible. It would be an inappropriate solution to the problem, but the use of SelectMany
can allow you to filter and project at the same time. This can be done by projecting the item to either a one item sequence containing the value that you want to project it to or an empty sequence based on the predicate.
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();
Of course, every time you use this code, a kitten is killed. Don't kill kittens; use the earlier query instead.
Upvotes: 12
Reputation: 23113
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();
First filter, then select. For this solution you need to have a navigation property so you can do u.Membership.Email
instead of the membershipUser.Email
.
My users look something like:
public class UserProfile
{
// other properties
public virtual Membership Membership { get; set; }
}
where Membership
is the entity representing the membership table and is mapped via:
modelBuilder.Entity<Membership>()
.HasRequired<UserProfile>(m => m.User)
.WithOptional(u => u.Membership);
Then you can select everything with one query. Some other solutions here also work fine, but every call to Membership.GetUser(u.UserName)
results in one additional DB call.
Upvotes: -1