I am trying to add a user to a team by searching through a list of users.
I am using linq to search through the data to find a match based on a number of fields
data = data.Where(x =>
x.Name.ToLower().Contains(filter) ||
x.Surname.ToLower().Contains(filter) ||
x.PhoneNumber.ToLower().Contains(filter) ||
x.Email.ToLower().Contains(filter) ||
x.AthleteTeams.All(x => x.Team.Name.Contains(filter)) ||
x.AthleteTeams.All(x => x.Athlete.AthleteGender.Equals(filter)) ||
x.AthleteTeams.All(x => x.Athlete.AthleteRace.Equals(filter))
);
However, the results work only with name, surname and email.
When all the fields are searched it gives back seemingly random users instead of those that meet the specified filter
I guess that you dont want to find all users which AthleteTeams have ALL the same team-name, for example. That would mean you have multiple teams with a similar name.
I guess that you actually want to find all users which AthleteTeams contain at least ONE team with matching name, for example. So use Any and also use only one query of AthleteTeams instead of three:
....
x.AthleteTeams.Any(x => x.Team.Name.Contains(filter)
|| x.Athlete.AthleteGender.Equals(filter)
|| x.Athlete.AthleteRace.Equals(filter))
However, i find this filter logic strange and error-prone. You will have a slow search performance and many false matches, because always something matches and the user doesn't know why. Better provide a search through the most common properties like the names and phone-number, don't start to search before the user enters at least 3 characters and provide a specific team-search.
However, the results work only with name, surname and email.
I think PhoneNumber should be fine too.
When all the fields are searched it gives back seemingly random users instead of those that meet the specified filter
Enumerable.All for empty collection will always be true:
Console.WriteLine(new int[]{}.All(i => false)); // prints True
Try using something like:
data = data.Where(x =>
... || (x.AthleteTeams.Any() && (
x.AthleteTeams.All(x => x.Team.Name.Contains(filter)) ||
x.AthleteTeams.All(x => x.Athlete.AthleteGender.Equals(filter)) ||
x.AthleteTeams.All(x => x.Athlete.AthleteRace.Equals(filter))))
);
Also possibly you actually need Any instead of All in the first place:
data = data.Where(x =>
...
|| x.AthleteTeams.Any(x => x.Team.Name.Contains(filter)
|| x.Athlete.AthleteGender.Equals(filter)
|| x.Athlete.AthleteRace.Equals(filter))
);
Related
I need to filter a list on multiple value.
It works well with basic type like string but I need to filter on 2 others filters build on list like that :
predicate.And(i => i.countries.Find(c => c.Code == country).Code == country)
First of all I'm not sure it is the good way to build this sort of predicate.
Second when predicate is built it looks like that :
{i => (i.countries.Find(c => (c.Code == value(Portail.Controllers.ProfilsController+<>c__DisplayClass21_0).country)).Code == value(Portail.Controllers.ProfilsController+<>c__DisplayClass21_0).country)}
And of course it doesnt work.
Is it possible to do such a thing and if yes, how?
I'm querying the Azure Cosmos DB and need to apply a filter based on an array of Search terms.
Something like below:
searchTerms = searchTerms.Where(s => s != null).Select(f => f.ToLower()).ToArray();
query = query.Where(uci => searchTerms.Any(f => uci.User.FirstName.ToLower().Contains(f))
|| searchTerms.Any(f => uci.User.LastName.ToLower().Contains(f))
|| searchTerms.Any(f => uci.User.Company.Name.ToLower().Contains(f)));
However the query fails with an error "Input is not of type IDocumentQuery"
So, "Any" is not supported by Microsoft.Azure.Cosmos.
The alternative was to use the sql query instead of using linq. SQL has 2 options "Array_contains" or "Contains" but that doesn't work because
Array_contains does a "=" check on the array elements rather than substring check
eg. SELECT ARRAY_CONTAINS(["apples", "strawberries", "bananas"], "apples") AS b1
Contains works on a single string
eg. SELECT CONTAINS("abc", "ab") AS c1
cosmos array_contains
This link speaks about the "like" keyword, however that also works on a single string.
The closest solution that I could come up with is below
searchTerms = searchTerms.Where(s => s != null).Select(f => f.ToLower()).ToArray();
foreach (string term in searchTerms) {
query = query.Where(uci => uci.User.FirstName.ToLower().Contains(term)
|| uci.User.LastName.ToLower().Contains(term)
|| uci.User.Company.Name.ToLower().Contains(term));
}
This will add a "where" clause for each search term which is more of a hack than a solution.
Has anyone run across such situation? Any optimal suggestions?
Basically, I have a list of reports, and each report belongs to an area. Each user has permissions in each area. I need to list the reports that my user can see, that is, the reports that belongs to areas in which the logged user has at least read permissions.
I'm using linq expression to create a predicate and run through a detachedQuery.
I've been working on this issue for a while, found many suggestions, such as: - LinqKit
- https://www.tabsoverspaces.com/233644-playing-with-parameters-limit-on-sql-server-with-entity-framework
- Entity Framework Hitting 2100 Parameter Limit
None seem to be my case since I'm not exactly retrieving a list, I'm just running a predicate.
Using "contains" made get to this problem with 2100 parameters because I have more than 3000 areas in my database. What I tried to do first was to divide areas in two lists, the first includes areas that I have permission, the second includes not permitted areas. The smallest list goes to my predicate. This approach worked fine for a while, but now I have more than 4500 areas. Therefore that does not work anymore.
var predicate = PredicateBuilder.Create<Report>();
var areasWithPermission = user.Permissions.Where(v => v.Access != AccessType.NotAllowed).Select(v => v.Area.Id).ToList();
var areasWithoutPermission= user.Permissions.Where(v => v.Access == AccessType.NotAllowed).Select(v => v.Area.Id).ToList();
var predicateSearchPermissions = PredicateBuilder.Create<Report>();
if (areasWithPermission > areasWithoutPermission) {
predicateSearchPermissions = predicateSearchPermissions.OrElse(a => !areasWithoutPermission.Contains(a.Area.Id))
}
else
{
predicateSearchPermissions = predicateSearchPermissions.OrElse(a => areasWithPermission .Contains(a.Area.Id))
}
predicate = predicate.AndAlso(predicateSearchPermissions);
Here is a simple representation of my environment:
Dont bring the permissions into memory just to send them down. "Join" the tables in a query instead.
Something like:
var reports = from r in reports
let areasWithPermission = user.Permissions.Where(v => v.Access != AccessType.NotAllowed).Select(v => v.Area.Id)
let areasWithoutPermission = user.Permissions.Where(v => v.Access == AccessType.NotAllowed).Select(v => v.Area.Id)
where areasWithPermission.Any(id => id == r.Area.Id) ||
!areasWithoutPermission.Any(id => id == r.Area.Id)
select r;
I have the following query:
var query = _projectEmployeesBLL.GetAll()
.Where(x => x.EmployeeID == employee.EmployeeID
&& x.project.isActive == true
&& x.project.isDelected == false)
.Select(x => new
{
x.project.ProjectID,
ProjeAdı = x.project.Name,
});
The problem is that I make the property ProjeAdı in the Select clause. I want the property to match the sql column, Proje Adı (note the space). How can I do that?
while this might not be useful in your case, someone else looking for a way can use the 'DisplayName' attribute that can come in handy when enumerating properties as propertiesDescriptor.
I had a similar requirement.
here's how to use it-
[DisplayName("Father Name")]
public string FatherName{get;set;}
then access it using
propertyDescriptor.DisplayName
or byways shared here
aliases cant have spaces between them , just like any variable name cant have white space, they are considered as bad naming conventions, you can use _(underscore) eg. Proje_Adi
I have a simple custom table with a search/filter field. I leave the implementation of the search up to each use of the table.
So let's say I have users in my table and I want to search for them. I want to search both in users firstname, lastname and also any role they are in.
This would probably do the trick
searchString = searchString.ToLower();
query = query.Where(
x =>
x.FirstName.ToLower().Contains(searchString)
||
x.LastName.ToLower().Contains(searchString)
||
x.Roles.Any(
role =>
role.Name.ToLower().Contains(searchString)
)
);
But now I want to search/filter on multiple words. First I get an array of all separate words.
var searchStrings = searchString.ToLower().Split(null);
I tried the following but it does not fulfill my requirements listed further down as it returns any user where any word is matched in any field. I need that all words are matched (but possibly in different fields). Se below for more details.
query = query.Where(
x =>
searchStrings.Any(word => x.FirstName.ToLower().Contains(word))
||
searchStrings.Any(word => x.LastName.ToLower().Contains(word))
//snipped away roles search for brevity
);
First let me produce some data
Users (data)
Billy-James Carter is admin and manager
James Carter is manager
Billy Carter has no role
Cases
If my search string is "billy car" I want Billy-James and Billy returned but not James Carter (so all words must match but not on same field).
If my search string is "bil jam" or even "bil jam car" I only want Billy-James returned as he is the only one matching all terms/words. So in this the words bil and jam were both found in the FirstName field while the car term was found in the LastName field. Only getting the "car" part correct is not enough and James is not returned.
If I search for "car man" Billy-James and James are both managers (man) and named Carter and should show up. should I search for "car man admi" then only Billy-James should show up.
I am happy to abandon my current approach if better is suggested.
I cannot think of a way to wrap what you're looking for up into a single LINQ statement. There may be a way, but I know with EF the options are more limited than LINQ on an object collection. With that said, why not grab a result set from the database with the first word in the split, then filter the resulting collection further?
var searchWords = searchString.ToLower().split(' ');
var results = dataBase.Where(i => i.FirstName.ToLower().Contains(searchWords[0])
|| i.LastName.ToLower().Contains(searchWords[0])
|| i.Role.ToLower().Contains(searchWords[0]));
if(searchWords.Length > 1) {
for(int x = 1; x < searchWords.Length; x++) {
results = results.Where(i => i.FirstName.ToLower().Contains(searchWords[x])
|| i.LastName.ToLower().Contains(searchWords[x])
|| i.Role.ToLower().Contains(searchWords[x]));
}
}
Your final content of the results collection will be what you're looking for.
Disclaimer: I didn't have a setup at the ready to test this, so there may be something like a .ToList() needed to make this work, but it's basically functional.
Update: More information about EF and deferred execution, and string collection search
Given we have the schema:
Employee:
FirstName - String
Last Name - String
Roles - One to Many
Role:
Name - String
The following will build a query for everything you want to find
var searchTerms = SearchString.ToLower().Split(null);
var term = searchTerms[0];
var results = from e in entities.Employees
where (e.FirstName.Contains(term)
|| e.LastName.Contains(term)
|| e.Roles.Select(r => r.Name).Any(n => n.Contains(term)))
select e;
if (searchTerms.Length > 1)
{
for (int i = 1; i < searchTerms.Length; i++)
{
var tempTerm = searchTerms[i];
results = from e in results
where (e.FirstName.Contains(tempTerm)
|| e.LastName.Contains(tempTerm)
|| e.Roles.Select(r => r.Name).Any(n => n.Contains(tempTerm)))
select e;
}
}
At this point the query still has not been executed. As you filter the result set in the loop, this is actually adding additional AND clauses to the search criteria. The query doesn't execute until you run a command that does something with the result set like ToList(), iterating over the collection, etc. Put a break point after everything that builds the query and take a look at it. LINQ to SQL is both interesting and powerful.
More on deferred execution
The one thing which needs explanation is the variable tempTerm. We need a variable which is scoped within the loop so that we don't end up with one value for all the parameters in the query referencing the variable term.
I simplified it a bit
//we want to search/filter
if (!string.IsNullOrEmpty(request.SearchText))
{
var searchTerms = request.SearchText.ToLower().Split(null);
foreach (var term in searchTerms)
{
string tmpTerm = term;
query = query.Where(
x =>
x.Name.ToLower().Contains(tmpTerm)
);
}
}
I build a much bigger query where searching is just a part, starting like this
var query = _context.RentSpaces.Where(x => x.Property.PropertyId == request.PropertyId).AsQueryable();
above search only uses one field but should work just fine with more complex fields. like in my user example.
I usually take the apporach to sort of queue the queries. They are all executed in one step at the database if you look with the diagnostic tools:
IQueryable<YourEntity> entityQuery = context.YourEntity.AsQueryable();
foreach (string term in serchTerms)
{
entityQuery = entityQuery.Where(a => a.YourProperty.Contains(term));
}