starts with over an array - c#

I have a function like so
public List<Entry> GetEntriesForSlider(int first, int max, List<string> NameLetters)
{
//Some code
}
Inside this code, I want to search along a database, to return every result that has the firstname starting with a letter within the NameLetters.
So if I pass in the array NameLetters = ["a","b","c"]
Then it will return results such as
Amy
Bert
Aaron
Chris
It should be noted that I am ideally looking to use some sort of linq statement such as...
entries.Where(x => x.FirstName.StartsWith(partofArray));
If at all possible.
EDIT : I previously had the following :
var entries = _repository.All<Entry>().Skip(first).Take(max);
if (NameLetters != null && NameLetters.Count > 0)
entries = entries.Where(x => NameLetters.Contains(x.FirstName[0].ToString()));
But what I found was, it enumerated the query (I think) before running the where statement. Possibly because trying to access the first letter of firstname (Or the ToString).

If you're just looking to match the first letter try:
entries.Where(x => partofArray.Contains(x.FirstName[0]));
If you need to worry about null or empty strings a safer version would be:
entries.Where(x =>
!string.IsNullOrEmpty(x.FirstName) &&
partofArray.Contains(x.FirstName[0])
);
If you want to use variable-length strings try:
entries.Where(x =>
partofArray.Any( p=> x.FirstName.StartsWith(p))
);

Perhaps you should take a look at predicate builder.
Then something like
IQueryable<Entry> GetThingsThatBeginWithA(String[] prefixes)
{
var predicate = PredicateBuilder.False<Entry>();
foreach (String prefix in prefixes)
predicate = predicate.Or(p => p.FirstName.StartsWith(prefix));
return database.Entries.Where(predicate);
}
This query should correctly compile to a single query to the SQL store.
Update: It can also be done without predicate builder, but I find that working with expression trees without it is really tedious.

Related

Find String matches any in List of Strings

How to find string with a exact match in the list of string.
var inv_attr_string_id = inv_attr
.Where(x => ItemStringVal.Contains(x.STRING_VAL))
.Select(x => x.ID).ToList();
ItemStringVal Contains list of Strings like "0030", "1433", "2019" etc ... Now I am trying to match it with the database in such a way that if it match exactly and all the three strings then it returns me the list of IDs matched ... else it should return null.
I tried
List<int> inv_attr_string_id = new List<int>();
foreach (var StrItem in ItemStringVal)
{
inv_attr_string_id.AddRange
(
inv_attr.Where(x => x.STRING_VAL.Contains(StrItem))
.Select(x => x.ID).ToList()
);
}
I have tried .Any as well but I got an error saying "Internal .NET Framework Data Provider error 1025"
I was thinking if I could be able to write it the way it creates a query of AND condition such as it should match (Exactly) all the input strings.
One Liner could be: Select IDs if all the string matches. Else return null
If I understand the problem - You have a list of strings which is your Input Data. You also have a List of patterns that may match with Data. You want to find the pairs of [Data, Pattern]?
Upfront this can be solved in O(N^2).
Psuedo Logic be like:
Foreach item in DataCollection
Foreach pattern in PatternCollection
if(Regex.IsMatch(item, pattern)) Then collect the item & pattern in some place
Hope this gives some starting point for you to solve the problem.
You can try linq to get all those records from db which are exist int your list
var result = inv_attr.AsEnumerable().Where(x => ItemStringVal.Exists(y => y == x.STRING_VAL)).Select(x => x.Id).ToList();

Can anybody tell me why this linq query won't work?

Sorry if I'm missing something really obvious, I've been coding for a lot of hours in a row and my brain is crawling to a halt. I have the following statement:
var hcn = "";
var forename = "";
var surname = "";
foreach (var casenoteResult in casenoteResults)
{
personResults.AddRange(_ctx.People.Where
(x => x.PAS_INT_NO == casenoteResult.PAS_INT_NO
&& x.CSA_NO.Contains(hcn)
&& x.FORENAMES.ToLower().Contains(forename.ToLower())
&& x.SURNAME.ToLower().Contains(surname.ToLower()))
.ToList());
}
And I get no result. The only thing I'm really looking for is the casenote. Yet if I comment out each of the '&&'s, so I'm left with this:
foreach (var casenoteResult in casenoteResults)
{
personResults.AddRange(_ctx.People.Where
(x => x.PAS_INT_NO == casenoteResult.PAS_INT_NO)
.ToList());
}
I get 1 result, which is what I'm expected.
Can anyone help me? Why does the first statement not return this 1 result? Could it be that some of the fields that I'm comparing the empty strings to are null? The one record that gets found doesn't have any nulls in it. I'm lost here. Please help my poor battered brain!
If I were you, I would re-write this code like below. It's safer to build the queryable in parts to make sure you have a good handle on which values you are actually passing in to the query. The reason why you are not getting any rows is probably because the query values going in to the query is not what you think they are or your database doesn't treat empty string as a wildcard. (Because based on what you posted, you are checking if a string contains an empty string which is always true in C# but may not be true for your database provider).
var queryable = _ctx.People.Where(w => caseNoteResults.Select(s => s.PAS_INT_NO).Contains(w.PAS_INT_NO));
queryable = string.IsNullOrEmpty(hcn) ? queryable : queryable.Where(w => w.CSA_NO.Contains(hcn, StringComparison.InvariantCulture));
queryable = string.IsNullOrEmpty(forename) ? queryable : queryable.Where(w => w.FORENAMES.Contains(forename, StringComparison.InvariantCultureIgnoreCase));
queryable = string.IsNullOrEmpty(surname) ? queryable : queryable.Where(w => w.SURNAME.Contains(surname, StringComparison.InvariantCultureIgnoreCase));
personResults.AddRange(queryable.ToList());
The idea is, if your hcn, forename and surname are empty, no point in checking them.
Also, make sure that you handle nulls safely if each of these fields are nullable.

EF filtering/searching with multiple words

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

Add on condition with Lambda

Is there a way I can do something like this with Lambda expressions?
responses.Add(sr).Where(v.Responses.TryGetValue(v.responseType, out sr));
I want to use lambda expressions or a ternary operator instead of a typical if expression.
NB:
responses is a List<string> type.
v.Responses is a Dictionary of <enum ResponseType, string>
v is some object
sr is a string.
What you want to do is:
string sr;
if (v.Responses.TryGetValue(v.responseType, out sr))
responses.Add(sr);
There is no way to ease the syntax and get the same performance.
But you could do:
responses.AddRange( v.Responses.Where( p => p.Key == v.responseType )
.Select( p => p.Value ) );
You may want to think about what the last one is doing, because it is kind of stupid...
EDIT: the reason why it is stupid is because the last expression translates into:
foreach(var pair in v.Responses)
{
if (pair.Key == v.responseType)
responses.Add(pair.Value);
}
So if your ResponseType enumeration had 6 million entries, the program would iterate over the entire set of keys to find the correct entry. In your case, since you already know the key, you should use v.Responses[key] as it is extremely fast (see in which cases dictionaries must be use).
LINQ is not supposed to modify collections.
Couldn't you simply do something like this:
string sr;
if(v.Responses.TryGetValue(v.responseType, out sr))
responses.Add(sr);
If I understand your question correctly, this might do what you're looking for.
Based on your example code, I'm assuming that your object "v" contains a field named "responseType" of type RepsonseType.
var responses = v.Responses
.Where(r => r.Key == v.responseType)
.Select(r => r.Value)
.ToList();

Linq - doesn't start with any prefix in range of prefixes

I need to create a Linq query that would have the following logic:
IEnumerable<string> prefixes = GetListOfPrefixesFromSomewhere();
IQueryable<Record> myQuery = GetAllRecordsFromRepository();
foreach (string prefix in prefixes)
{
myQuery = myQuery.Where(x => !x.Field.StartsWith(prefix));
}
This would obviously result in a large IQueryable which can then be executed.
Is there a nice elegant way to express this is a single Linq statement?
You can at least try this:
// Only call ToList if you need to, of course... but I think EF/LINQ To SQL
// will need it as a list (or array)
List<string> prefixes = GetListOfPrefixesFromSomewhere().ToList();
IQueryable<Record> query = GetAllRecordsFromRepository()
.Where(x => !prefixes.Any(prefix => x.Field.StartsWith(prefix)));
Quite what the SQL will look like, I don't know - but I think it's logically what you want, which is usually a good start.

Categories

Resources