Can SELECT and WHERE LINQ clause be combined? - c#

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?

Conceptually you actually have three operations here:
project the user name to a membership user
filter out null membership users
project the membership users to a model
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.

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).

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();

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);

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.

Related

C# linq how to get property from predicate list when condition is met

I have the following linq query
var usersToNotify = trainingUsers.Where(x => delegatesToBeReminded.Any(d => d.UserGuid == x.UserGuid))
.Select(x => new RecipientDetail
{
FullName = x.FullName,
Email = x.Email,
// get property from delegatesToBeReminded
})
.ToList();
In the example above, i have trainingusers and delegatesToBeReminded list. i want to retrieve the matching record found in trainingusers and create custom type, with fullname, email from trainingusers and additional property from delegatesTobeReminded.
Can anyone help me how to do this?
Can i use something like this?
var x = from tu in trainingUsers
join d in delegatesToBeReminded on tu.UserGuid equals d.UserGuid
select new RecipientDetail
{
FullName = tu.FullName,
Email = tu.Email,
Session = d.Session
};
Thanks
Easiest would be to use a join, as you suggested:
trainingUsers.Join(
delegatesToBeReminded,
user => user.UserGuid,
delegateToBeReminded => delegateToBeReminded.UserGuid,
(user, delegateToBeReminded) => new RecipientDetail
{
FullName = user.FullName,
Email = user.Email,
Delegate = delegateToBeReminded,
});
(Or you can write the equivalent in linq query syntax, as you did).
Another way is to rewrite this in linq query syntax, using let:
from user in trainingUsers
let delegateToBeReminded = delegatesToBeReminded.FirstOrDefault(d => d.UserGuid == user.UserGuid)
where delegateToBeReminded != null
select new RecipientDetail
{
FullName = user.FullName,
Email = user.Email,
Delegate = delegateToBeReminded,
}
Note that these differ depending on what happens if there is more than one delegate for a particular user. The first creates a new RecipientDetail object for each user/delegate pair; the second creates a RecipientDetail object per user, and picks the first delegate.

Access list items by name to assign to variables

I am sure the answer to this is everywhere but I am struggling to find it, perhaps I am not wording it correctly?
I have a list of items as shown:
What I want to then do is something like:
var phoneNo = res("phone_number").Value;
But what is the actual syntax?
EDIT:
Here's something I've tried to no success:
Looks like you are trying to get value from your claims. You can do so as below:
var userClaims = context.HttpContext.User as ClaimsPrincipal;
Get you claim value:
if (userClaims.Claims.Where(x => x.Type == "phone_number").FirstOrDefault() != null)
{
var phoneNumberClaim = User.Claims.Where(x => x.Type == "phone_number").FirstOrDefault().Value;
}
There is a special structure in programming for such tasks. It is called Dictionary.
I think your ToClaims method should return Dictionary, so you can access values by whatever key you want. Also you can cast your list using .ToDictionary(...), where you could split string by ":" for key and value.
From the feedback given I would like to show two of the best methods I took away from this.
The method I went with:
//set up a dictionary
var claims = ctx.User.ToClaims().ToDictionary(claim => claim.Type, claim => claim.Value);
//access as follows
var phoneNo = claims["phone_number"];
var firstName = claims["given_name"];
//etc.
Another good way:
//set up IEnumerable
var claims = ctx.User.ToClaims();
//access as follows
var phoneNos = claims.FirstOrDefault(c => c.Type == "phone_number")?.Value;
var firstName = claims.FirstOrDefault(c => c.Type == "given_name")?.Value;
//etc.

Write attributes from list to var

I have the following piece of code:
var Attributes = db.Users.Where(u => u.UserId == PwRetreival.uId).Select(u => new { u.Name, u.UserId }).ToList();
user.Name = Attributes(SOMETHING?)
user.UserId = Attributes(SOMETHING?)
I have no idea how i would write the selected attributes to my model variables. I guess it doesn't know the type of the attribute when i write it like this?
This line returns a list of anonymous objects:
var Attributes = db.Users
.Where(u => u.UserId == PwRetreival.uId).Select(u => new { u.Name, u.UserId }).ToList();
Therefore, you can either iterate the list or index it:
user.Name = Atrributes[0].Name;
NOTE: Since you are getting the item by its Id, I would use Single or SingleOrDefault and not Where. See below for more.
Use Single
If you expect a single item to be returned, then do not use Where but use Single instead. Then you can do this:
user.Name = Attributes.Name;
Use SingleOrDefaut
If you expect a single item or no item, then use SingleOrDefault but check the value before using it like this:
if (Attributes != null)
{
user.Name = Attributes.Name;
}
There are also First, FirstOrDefault, Last and LastOrDefault.
As it is of type List, you need to use FirstOrDefault() to get the first record from the list (assuming that your Where clause have enough conditions to get the exact record you want).
var Attributes = db.Users.Where(u => u.UserId == PwRetreival.uId).Select(u => new { u.Name, u.UserId }).ToList().FirstOrDefault();
if (Attributes != null)
{
user.Name = Attributes.Name;
user.UserId = Attributes.UserId;
}
Attributes now is a list of an anonymous type containing Name and UserId.
user.Name = Attributes[0].Name
user.UserId = Attributes[0].UserId
... Would get the name and id of the first user, if the list contains at least 1 element.
You can also do:
foreach(var user in Attributes)
{
// var userName = user.Name;
// var userId = user.UserId;
}
... to iterate through all users. In this case, you don't even need the ToList() method in your query;
However, it seems like this query should return just one user. In this case, you can change your query to
var Attributes = db.Users.Where(u => u.UserId == PwRetreival.uId).Select(u => new { u.Name, u.UserId }).FirstOrDefault();
... and now Attributes has only 1 object with a Name and a UserId and you can access it like:
user.Name = Attributes.Name
user.UserId = Attributes.UserId
As pointed out by #Chris, if you can assume that your query is going to return 0 or 1 element, you should use SingleOrDefault(). If it should return just 1 element, you should use Single(). If the result contains more elements than it will throw an exception. And when you use XOrDefault you should always check for null afterwards.

Get all usernames from database to a list in MVC 4

I have this controller:
public ActionResult NameSearch(SearchModel userSearch)
{
if (ModelState.IsValid)
{
var db = new reviewlogsEntities();
return View(db.logrecords.Where(m => m.username == userSearch.userName).ToList());
}
return RedirectToAction("index", "home");
}
My question is the user that is currently searching for logs is looking for a username once they have a username they can output a list. The problem I am having right now is what would my requirements be if they don't enter a username. What should happen is all users and their logs should come up. I could just make an if statement that says if left empty just do
return View(db.logrecords.ToList());
I feel like there is another way I could do it without having to do that. Because my thought process is, what if the search needs to be more complex. Where they might not enter a username but they could enter a date they are looking specifically for, or vise versa. I couldn't really do if statements then without it being super messy. Any help would really be appreciated!
You can add an if condition and do more filtering on the logrecords dbset as needed.
Assuming db is an object of your DbContext and db.logrecords is of type DbSet<LogRecord>
IQueryable<LogRecord> recordsDbSet = db.logrecords;
if(!String.IsNullOrEmpty(userSearch.UserName))
{
recordsDbSet = recordsDbSet.Where(m => m.username == userSearch.userName)
}
var resultList = recordsDbSet.ToList();
return View(resultList);
If you are planning to create more search fields in the future, you will want to set up your query to accommodate that without refactoring. Here's is a common approach:
var searchResults = db.logresults.AsQueryable();
//User Name
if (userSearch.username != string.empty)
searchResults = searchResults.Where(l => l.username == userSearch.username);
//From Date
if (userSearch.FromDate != DateTime.MinValue)
searchResults = searchResults.Where(l => l.CreatedDate >= userSearch.FromDate);
//To Date
if (userSearch.ToDate != DateTime.MinValue)
searchResults = searchResults.Where(l => l.CreatedDate <= userSearch.ToDate);
//Category (because logs always end up categorized)
if (userSearch.Category != LogCategories.All) //assuming enum here
searchResults = searchResults.Where(l => l.Category == userSearch.Category);
Setting up your method like this is easy to follow and extend when new search criteria are added, and the resulting query only includes the parameters entered by the user.

Linq query Contains method with where condition with case sensitive less search

I am trying get the data which is contains single word with in the word.Like below query.
List<Models.tbluser> memberslist = new List<Models.tbluser>();
var obct = (from memlist in objcontext.tblusers
where memlist.logname.Contains(member)
select new
{
userid = memlist.userid,
logname = memlist.logname,
decription = memlist.description
}).ToList();
foreach (var item in obct)
{
memberslist.Add(new tbluser
{
userid = item.userid,
logname = item.logname,
description = item.decription
});
}
return Json(memberslist);
But here my problem is i need to search with out case sensitive.
For example
If i search with 'a' i need to get data like Admin,Administrator,User Data.
But i am not getting all these because i am searching with Contains() method.Please let me know how can i get all value either the search value is case sensitive less also.
Change your where condition to be:
memlist.logname.ToUpper().Contains(member.ToUpper())
As a side note, you can shorten your query a bit (you don't need to create an intermediary list):
var memberslist = objcontext.tblusers
.Where(x => x.logname.ToUpper().Contains(member.ToUpper())
.AsEnumerable()
.Select(x => new tbluser
{
userid = x.userid,
logname = x.logname,
decription = x.description
})
.ToList();
return Json(memberslist);
You can change them to Lower or Upper Case when checking the condition using ToLower() or ToUpper():
var obct = (from memlist in objcontext.tblusers
where memlist.logname.ToLower().Contains(member.ToLower())
select new
{
userid = memlist.userid,
logname = memlist.logname,
decription = memlist.description
}).ToList();

Categories