Linq - Add multiple "dynamic" conditions - linq

Problem
I have an IQueryable, and I want to search it based on roles. An user can have multiple roles so I want to be able to add multiple search conditions (one on top of another).
public void OnGet()
{
var productionUnits = _context.ProductionUnits;
IQueryable query = productionUnits;
if (User.IsInRole(CustomRole.AdministratorUAP1))
{
query = productionUnits.Where(c => c.Id == (int)ProductionUnitEnum.UAP1);
}
if (User.IsInRole(CustomRole.AdministratorUAP2))
{
query = productionUnits.Where(c => c.Id == (int)ProductionUnitEnum.UAP2);
}
...
}
Expected Output
If the user is in multiple roles, for example UAP1 and UAP2, I want the query to get both of them in the Where clause. Is there any way to achieve this (I know I could do List.AddRange(), but I really want to update the query instead). Is there any way to achieve this?

I would creeate a list of roles for the user and use Contains in the query:
var roleIds = new List<int>();
if (User.IsInRole(CustomRole.AdministratorUAP1))
{
roleIds.Add(ProductionUnitEnum.UAP1);
}
if (User.IsInRole(CustomRole.AdministratorUAP2))
{
roleIds.Add(ProductionUnitEnum.UAP2)
}
var query = productionUnits.Where(c => roleIds.Contains(c.Id));
That will add an IN clause to your query for those two roles. If you have more than two roles just add them to the list as appropriate.

Each Linq function returns an IQueryable<>, so just keep reusing that:
IQueryable<T> query = productionUnits; // note the <T>
if (User.IsInRole(CustomRole.AdministratorUAP1))
query = query.Where(c => c.Id == (int)ProductionUnitEnum.UAP1);
if (User.IsInRole(CustomRole.AdministratorUAP2))
query = query.Where(c => c.Id == (int)ProductionUnitEnum.UAP2);
If the user is in multiple roles, for example UAP1 and UAP2
Then your database is fundamentally broken, you're checking those values against a single field: c.Id. One number can't be both values at the same time.

Related

LINQ GroupBy on single property

I am just not understanding the LINQ non-query syntax for GroupBy.
I have a collection of objects that I want to group by a single property. In this case Name
{ Id="1", Name="Bob", Age="23" }
{ Id="2", Name="Sally", Age="41" }
{ Id="3", Name="Bob", Age="73" }
{ Id="4", Name="Bob", Age="34" }
I would like to end up with a collection of all the unique names
{ Name="Bob" }
{ Name="Sally" }
Based on some examples I looked at I thought this would be the way to do it
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
But I ended up with a collection with one item. So I though maybe I was over complicating things with the projection. I tried this
var uniqueNameCollection = Persons.GroupBy(x => x.Name).ToList();
Same result. I ended up with a single item in the collection. What am I doing wrong here? I am just looking to GroupBy the Name property.
var names = Persons.Select(p => p.Name).Distinct().ToList()
If you just want names
LINQ's GroupBy doesn't work the same way that SQL's GROUP BY does.
GroupBy takes a sequence and a function to find the field to group by as parameters, and return a sequence of IGroupings that each have a Key that is the field value that was grouped by and sequence of elements in that group.
IEnumerable<IGrouping<TSource>> GroupBy<TSource, TKey>(
IEnumerable<TSource> sequence,
Func<TSource, TKey> keySelector)
{ ... }
So if you start with a list like this:
class Person
{
public string Name;
}
var people = new List<Person> {
new Person { Name = "Adam" },
new Person { Name = "Eve" }
}
Grouping by name will look like this
IEnumerable<IGrouping<Person>> groups = people.GroupBy(person => person.Name);
You could then select the key from each group like this:
IEnumerable<string> names = groups.Select(group => group.Key);
names will be distinct because if there were multiple people with the same name, they would have been in the same group and there would only be one group with that name.
For what you need, it would probably be more efficient to just select the names and then use Distinct
var names = people.Select(p => p.Name).Distinct();
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
Appears valid to me. .net Fiddle showing proper expected outcome: https://dotnetfiddle.net/2hqOvt
Using your data I ran the following code statement
var uniqueNameCollection = people.GroupBy(x => x.Name).Select(y => y.Key).ToList();
The return results were List
Bob
Sally
With 2 items in the List
run the following statement and your count should be 2.
people.GroupBy(x => x.Name).Select(y => y.Key).ToList().Count();
Works for me, download a nugget MoreLinq
using MoreLinq
var distinctitems = list.DistinctBy( u => u.Name);

LINQ to Entities does not recognize the method 'Boolean CheckMeetingSettings(Int64, Int64)' method

I am working with code first approach in EDM and facing an error for which I can't the solution.Pls help me
LINQ to Entities does not recognize the method 'Boolean
CheckMeetingSettings(Int64, Int64)' method, and this method cannot be
translated into a store expression.
My code is following(this is the query which I have written
from per in obj.tempPersonConferenceDbSet
where per.Conference.Id == 2
select new PersonDetials
{
Id = per.Person.Id,
JobTitle = per.Person.JobTitle,
CanSendMeetingRequest = CheckMeetingSettings(6327,per.Person.Id)
}
public bool CheckMeetingSettings(int,int)
{
///code I have written.
}
Please help me out of this.
EF can not convert custom code to SQL. Try iterating the result set and assigning the property outside the LINQ query.
var people = (from per in obj.tempPersonConferenceDbSet
where per.Conference.Id == 2
order by /**/
select new PersonDetials
{
Id = per.Person.Id,
JobTitle = per.Person.JobTitle,
}).Skip(/*records count to skip*/)
.Take(/*records count to retrieve*/)
.ToList();
people.ForEach(p => p.CanSendMeetingRequest = CheckMeetingSettings(6327, p.Id));
With Entity Framework, you cannot mix code that runs on the database server with code that runs inside the application. The only way you could write a query like this, is if you defined a function inside SQL Server to implement the code that you've written.
More information on how to expose that function to LINQ to Entities can be found here.
Alternatively, you would have to call CheckMeetingSettings outside the initial query, as Eranga demonstrated.
Try:
var personDetails = obj.tempPersonConferenceDbSet.Where(p=>p.ConferenceId == 2).AsEnumerable().Select(p=> new PersonDetials
{
Id = per.Person.Id,
JobTitle = per.Person.JobTitle,
CanSendMeetingRequest = CheckMeetingSettings(6327,per.Person.Id)
});
public bool CheckMeetingSettings(int,int)
{
///code I have written.
}
You must use AsEnumerable() so you can preform CheckMeetingSettings.
Linq to Entities can't translate your custom code into a SQL query.
You might consider first selecting only the database columns, then add a .ToList() to force the query to resolve. After you have those results you van do another select where you add the information from your CheckMeetingSettings method.
I'm more comfortable with the fluid syntax so I've used that in the following example.
var query = obj.tempPersonConferenceDbSet
.Where(per => per.Conference.Id == 2).Select(per => new { Id = per.Person.Id, JobTitle = per.Person.JobTitle })
.ToList()
.Select(per => new PersonDetails { Id = per.Id,
JobTitle = per.JobTitle,
CanSendMeetingRequest = CheckMeetingSettings(6327, per.Person.Id) })
If your CheckMeetingSettings method also accesses the database you might want to consider not using a seperate method to prevent a SELECT N+1 scenario and try to express the logic as part of the query in terms that the database can understand.

Filtering User table by MembershipUserCollection

So, I have ASP Membership set up in my application. I also have a separate User table for managing non-membership related data. During user management I need to make sure that my application queries both tables. I have the below Controller that returns a list of approved users, but it seems like there has to be a simpler way to accomplish this. What is a better way to do it?
QuoteExchangeDB _db = new QuoteExchangeDB();
[MyAuthorize(Roles = "Administrator")]
public ActionResult Admin()
{
MembershipUserCollection agents = Membership.GetAllUsers();
IEnumerable<MembershipUser> unfiltered = agents.Cast<MembershipUser>();
var filtered = unfiltered.Where(u => u.IsApproved);
List<User> users = new List<User>();
foreach (var item in filtered)
{
if (item.IsApproved)
{
Guid guid = (Guid)item.ProviderUserKey;
users.Add(_db.Users.Single(u => u.MembershipGuid.Equals(guid)));
}
}
return View(users);
}
This looks like you probably want a simple join in LINQ.
var filtered = Membership.GetAllUsers().Cast<MembershipUser>().Where(u => u.IsApproved);
var users = from f in filtered
join u in _db.Users on ((Guid)f.ProviderUserKey) equals u.MembershipGuid
select u;
You could probably make one statement out of that even:
var users = from f in Membership.GetAllUsers().Cast<MembershipUser>()
join u in _db.Users on ((Guid)f.ProviderUserKey) equals u.MembershipGuid
where f.IsApproved
select u;
Edit: Given that I'm not sure how joining an IEnumerable with an IQueryable might affect things/cause problems in this instance, here's a blog about doing that.

LINQ to Entities complex query

Is it possible ...??? I have 4 DropDownLists on my main page and the
user may select from any, all or some of
the DropDownLists. I am capturing their selection (or non-selection) using a SESSION
variable. What I would like to be able to do is pass the session
variable values to my Data Access Layer and build a WHERE clause
(maybe using StringBuilder) and then place that variable SOMEHOW into
my query expression. Is that possible??? Sorry, I'm a newbie. Thanks ~susan~
public class DLgetRestaurants
{
FVTCEntities db = new FVTCEntities();
public List<RESTAURANT> getRestaurants(string cuisineName, string priceName, string cityName)
[Build a string based on the values passed to the function]
{
var cuisineID = db.CUISINEs.First(s => s.CUISINE_NAME == cuisineName).CUISINE_ID;
List<RESTAURANT> result = (from RESTAURANT in db.RESTAURANTs.Include("CITY").Include("CUISINE").Include("Price")
where **[USE STRINGBUIDER EXPRSSION HERE]**
select RESTAURANT).ToList();
return result;
}
}
You can compose Where conditions which are linked by a logical AND relatively easy in LINQ extension method syntax:
var query = db.RESTAURANTs.Include("CITY").Include("CUISINE").Include("Price");
if (userHasSelectedInDDL1)
query = query.Where(r => r.PropertyForDDL1 == ValueFromDDL1);
if (userHasSelectedInDDL2)
query = query.Where(r => r.PropertyForDDL2 == ValueFromDDL2);
if (userHasSelectedInDDL3)
query = query.Where(r => r.PropertyForDDL3 == ValueFromDDL3);
if (userHasSelectedInDDL4)
query = query.Where(r => r.PropertyForDDL4 == ValueFromDDL4);
List<RESTAURANT> result = query.ToList();
For a much more flexible solution to build queries dynamically the Dynamic LINQ Library recommended by boca is probably the better choice.
I have done this in the past using the Dynamic Linq Library.

Linq - Simulate an OrWhere expression when building LINQ queries dynamically?

The code snippet below search allow the user to match a string against three fields in the table. If any of the fields match, the entry is included in the result. However, using Where to filter out the results is resulting in "the string must match all three fields" instead "the string can match any of the three fields".
Is there a way to simulate an OrWhere expression when building LINQ queries dynamically?
var foundUsers = from UserInfo user in entities.UserInfo
select user;
if (searchCompleteName)
{
foundUsers = foundUsers.Where(u => u.CompleteName.Contains(searchString));
}
if (searchPortalID)
{
foundUsers = foundUsers.Where(u => u.PortalID.Contains(searchString));
}
if (searchUsername)
{
foundUsers = foundUsers.Where(u => u.UserIdentity.Contains(searchString));
}
PS. I am using Entities Framework and LINQ to Entities, and am doing a MVC3 Web Application.
Try this:- http://www.albahari.com/nutshell/predicatebuilder.aspx
Not exactly pretty, but it would work.
var foundUsers = entities.UserInfo.Where(u =>
(searchCompleteName && u.CompleteName.Contains(searchString))
|| (searchPortalID && u.PortalID.Contains(searchString))
|| (searchUsername && u.UserIdentity.Contains(searchString));
You could also do this with a union. The union operator returns distinct results so there will not be any duplicates. I have no idea if EF can defer this to the database.
var foundUsers = Enumerable.Empty<UserInfo>().AsQueryable();
if (searchCompleteName)
{
foundUsers = foundUsers.Union(entities.UserInfo.Where(u => u.CompleteName.Contains(searchString)));
}
if (searchPortalID)
{
foundUsers = foundUsers.Union(entities.UserInfo.Where(u => u.PortalID.Contains(searchString)));
}
if (searchUsername)
{
foundUsers = foundUsers.Union(entities.UserInfo.Where(u => u.PortalID.Contains(searchString)));
}

Resources