The following Linq-to-SQL code does not return any customer results when I search for first name and last name, such as "Joe Smith". My syntax to concatenate FirstName and LastName is not correct - can you please help?
var searchText = "Joe Smith";
IQueryable<Customer> query = dc.Customer
.Where(cust => cust.Contact.Select(con => con.FirstName + " " + con.LastName).Contains(searchText));
var customers = query.AsEnumerable().ToArray();
You aren't respecting the case of the strings: "John" and "john" are two different people.
IQueryable<Customer> query = dc.Customer
.Where(cust =>
cust.Contact.Select(con =>
con.FirstName.ToUpperInvariant()
+ " " +
con.LastName.ToUpperInvariant())
.Contains(searchText.ToUpperInvariant()));
ToUpperInvariant() makes the string culture-insensitive and forces both your returned values and your search text to be case-insensitive by reading them as uppercase. Don't use lowercase in this way, as that can cause issues when you use more culture-sensitive approaches. VS 2017 will show a warning if you use lowercase like this and propose going to uppercase to fix it.
Also, remember that whitespace isn't always just empty. If you are still getting no results and should be, you may need to .Trim() at the end of the first and last name strings to drop carriage returns and whitespaces.
Finally, just a note as a suggestion; it's better to split a first name and last name into two separate searches than to combine first and last name, stick a space in it, and then search against the full name. This prevents surprises caused by you modifying the string before it's digested. For instance, if your input is "John " "Doe" because there's a space in the first name field, your code will create "John Doe". When it searches against "John Doe" it will find no records.
You can keep much of what you have with a slightly lazier search:
IQueryable<Customer> query = dc.Customer
.Where(cust =>
cust.Contact.Select(con =>
con.FirstName.ToUpperInvariant().Trim()
+
con.LastName.ToUpperInvariant().Trim())
.Contains(searchText.ToUpperInvariant()
.Replace(" ", string.Empty).Trim()));
That removes the space, forces it all to uppercase, removes whitespaces, and creates a search key of firstnamelastname...so now you're comparing JOHNDOE (string from the search) to JOHNDOE (input). It's a little sloppy but does the job in lightweight apps.
as far I understand here is your solution
hope it will solve your problem.
IQueryable<Customer> query = dc.Customer.Where(cust =>new {fullName=cust.FirstName+" "+cust.LastName}.fullName.Contains(searchText));
var customers = query.AsEnumerable().ToArray();
here is the edited solution please check it out and let me know
IQueryable<Customer> query = dc.Customer.Where(cust =>cust.Contact.Where(con=>new{FullName=con.FirstName+" "+con.LastName}.FullName.Contains(searchText)).Any());
var customers = query.AsEnumerable().ToArray();
While I absolutely appreciate the answers from the two contributors, they did not solve my specific need.
My solution... first get the CustomerIds from the Contacts table, then look up the customer records:
var customerIds = dc.Contact
.Where(c => (c.FirstName + " " + c.LastName).Contains(text))
.Select(c => c.CustomerId)
.Distinct()
.ToArray();
var customers = dc.Customer
.Where(c => customerIds.Contains(c.Id))
.ToArray();
Related
I'm a beginner c# coder and I have wrote a small code which counts how many different characters are in a string and returns a List. I wanted to make this code much more efficient.
string s1 = "noob";
List<int> count = new();
foreach(var item in s1.Distinct()){
count.Add(s1.Count(x => x == item));
}
The end result is exactly what I want, a List of int with values of 1,2,1.
However I'm fully aware that this is really inefficient (since I have to iterate through the string at each character).
I've tried using the LINQ GroupBy method but I was unable to assign it to either a list or to an int[] for some reason. (I don't know how to convert from IGrouping to array..)
My logic was in theory simple. Group all characters and for each group return how many of those characters are present and add that value to the array.
It pisses me off to no end that I'm unable to figure this one out on my own. I welcome any tips or guidance on this matter and thank you very much in advance.
You can use GroupBy instead of Distinct (we group equal charactes, select Count from each group and, finally, materialize as a list):
string s1 = "noob";
List<int> list = s1
.GroupBy(c => c)
.Select(group => group.Count())
.ToList(); // ToArray(); if you want an array
Let's have a look:
Console.Write(string.Join(", ", list));
Outcome:
1, 2, 1
My logic was in theory simple. Group all characters and for each group return how many of those characters are present and add that value to the array.
Good logic
It pisses me off to no end that I'm unable to figure this one out on my own.
Dmitry's given a good answer, but let's talk a bit about GroupBy, because it's confusing as ****
If you're used to SQL GROUP BY, this isn't much like that - it "stops half way"
In SQL a GROUP BY insists you have something you group on and for anything you dond't group, you have to supply some aggregation. Here's all the employee counts by department, and the max salary in the department:
SELECT department, COUNT(*), MAX(salary) FROM emp GROUP BY department
dept, name, salary
tech, jon, 100000
tech, jane, 120000
sales, tim, 90000
--results
tech, 2, 12000
sales, 1, 90000
LINQ doesn't do this when it groups; it runs the grouping but it gives you all the input data divided up into the groups and doesn't insist you do any aggregates
The same thing there but done by LINQ is:
employees.GroupBy(e => e.Department)
[
{ Key = "tech", self = [ { tech/jon/100000 }, { tech/jane/120000} ] },
{ Key = "tech", self = [ { sales/tim/90000 } ] }
]
That's some pseudo-json to describe what you get from a GroupBy. You get a list of groupings. A grouping is something "like a list", that has a Key property, and can be enumerated. If you ask for the Key you get what was grouped on (the string of the department name). If you enumerate the grouping, you get all the employees that have that Key
var groups = employees.GroupBy(e => e.Department);
foreach(var group in groups){
Console.WriteLine("Now looking at the contents of the " + g.Key + " group");
foreach(var employee in group)
Console.WriteLine(" It's " employee.Name);
}
Prints:
Now looking at the contents of the Tech group
It's Jon
It's Jane
Now looking at the contents of the Sales group
It's Tim
Because this list of things is.. well.. an enumerable list, you can call other LINQ methods on it
foreach(var group in groups){
Console.WriteLine("Now looking at the contents of the " + g.Key + " group");
Console.WriteLine(" The count is " + group.Count());
Console.WriteLine(" The max salary is " + group.Max(employee => employee .Salary));
}
Now looking at the contents of the Tech group
The count is 2
The max salary is 120000
Now looking at the contents of the Sales group
The count is 1
The max salary is 90000
By stopping half way and not forcing you to do aggregates, LINQ leaves itself open for really useful stuff
In short, GroupBy is simply a device that takes a single list:
Aaron
Albert
Bertie
Ben
Charlie
And turns it into a list of lists:
GroupBy(name => name[0]) //first char
A: [Aaron, Albert]
B: [Bertie, Ben]
C: [Charlie]
Just remember, that to avoid confusing yourself, name your variables well in your lambdas
list_of_names
.GroupBy(name => name[0])
.Select(list_of_names_with_same_key => list_of_names_with_same_key.Count())
Remind yourself that what you're selecting is a list within a list, You might want to not use a name like list_of_names_with_same_key for that; I use g for "grouping" and I remember that a grouping is some enumerable list with a Key property that defines some value common to all
It's not so useful in this example, but also note that GroupBy has more overloads that the basic one but the docs are really tough to read. It's quite common to use the overload that lets you choose a different value..
//produces something like a List<string department, List<Employee>>
employees.GroupBy(emp => emp.Department)
//produces something like a List<string department, List<int>>
employees.GroupBy(emp => emp.Department, emp => emp.Salary)
The second one produces just salaries in the list of grouped items, which makes it easier to e.g.:
Console.WriteLine(" The max salary is " + g.Max()); //g is a list of ints of salaies, not full employees
And finally, it might help demystify SelectMany too - SelectMany is basically the opposite of GroupBy. If GroupBy turns a 1 dimensional list into a 2 dimensional thing,. SelectMany turns a 2D thing into a 1D thing. The thing you give to SelectMany should be a list within a list, and you get a single list back out (outerlist.SelectMany(someInnerList) results in all the inner lists concatted together).
You could literally:
employees.GroupBy(emp => emp.Department).SelectMany(g => g)
and end up back where you started
var counts = "noob".GroupBy(c => c, (_, cs) => cs.Count());
Console.WriteLine("{0}", string.Join(",", counts)); // prints '1,2,1'
I am working on C# EntityFramework linq query. I am having trouble how to write the following scenario.
I have Person table and alias table and have foreign Key relationship between tables. If User search by "firstname lastname" or "lastname firstname" need to give the results if it present in persontable otherwise need look into alias table get the result. In database , the fullname is following format "lastname firstname" or "firstname lastname".
Person Table: (Id, Fullname) Alias Table:(Id PersonId Fullname)
I am splitting the name search by user using spaces and stores those strings in array.
I am able to successfully get the results from the Person table. I am having trouble getting the results from alias table.
Following is the query I am working:
.
string[] lsNames = val.Split(' ', ',', '&');
var a = this.context.Person
.Include(x => x.Alias)
.Where(x => x.Alias.Any(v =>
lsNames.All(n=>v.Name.ToLower().Contains(n.ToLower()))));
I need to return the person table and including the aliases. How to search list of values in the alias table using linq query.
The above query is giving me the error- evaluation time out. Could anyone please help me solving this problem.
If your application is enforcing that the stored full name is "{first} {last}" and-or "{last} {first}" and the issue is that you want to compare a user entry which could be any reasonable user format, then I'd consider parsing the input string, deciding on whether it's particularly valid, and then issuing specific conditions based on that.
For instance, if the data is stored as "{first} {last}":
if (string.IsNullOrEmpty(name))
return null; // or handle no name search provided.
var nameParts = name.Split(new [] {' ', ',', '&'}, StringSplitOptions.RemoveEmptyEntries);
var query = Context.Person.AsQueryable();
if (nameParts.Length == 1)
query = query.Where(x => x.Fullname.Contains(nameParts[0]));
else if (nameParts.Length == 2)
{
var combination1 = string.Join(" ", nameParts);
Array.Reverse(nameParts);
var combination2 = string.Join(" ", nameParts);
query = query.Where(x => x.Fullname == combination1 || x.Fullname == combination2 );
}
else
{ // More than 2 name components, so assume they are typing
// "{first} {middle} {last}" or "{last}, {first} {middle}"
var combination1 = string.Join(" ", nameParts);
// Shift the first element (last name) to the end
var firstElement = nameParts[0];
Array.Copy(nameParts, 1, nameParts, 0, nameParts.Length- 1);
nameParts[nameParts.Length - 1] = firstElement;
var combination2 = string.Join(" ", nameParts);
query = query.Where(x => x.Fullname == combination1 || x.Fullname == combination2);
}
var people = query.ToList();
This imposes set limits on how the input string will be searched. This assumes the database comparison by EF will be case insensitive, as per SQL Server /w most default correlations. If the database comparisons are case sensitive then hopefully the data is always stored with a consistent case (always upper or always lower) then the appropriate case can be applied above. Ideally just to the string fed into the expression, not both the evaluation string and the entity/db column. (More work for the DB)
The above scenario limits the checks that if a user types in a single word like "Peter" it will find "Peter Smith", or "Mitch Peterson" Where if they typed "Peter Smith" or "Smith, Peter" that would match on "Peter Smith" but not "Peter Smithe" for instance. Having a looser comparison across combinations will result in a lot more work for SQL to ultimately need to process. Introducing rules/expectations for input and validating those will result in more code, but simpler/faster queries. The risk of Contains-like querying is that this leaves the possibility that users can cripple your queries with strings like "I am an evil b or c or d or e" Imagine the OR'd like conditions that Contains combined with Any/'All` would produce for the combinations in that resulting array. It's generally safer to be more pessimistic with user input and let users refine their entry than trying to accommodate near matches, especially without dealing with specific columns like .FirstName and .LastName.
If you are getting timeout errors, you need to check your indexing to make sure Name is indexed and that the index is being used correctly. For example, if your column is set as varchar not nvarchar, SQL will do a type coersion in the where clause and may ignore your index as a result. You can force EF to not use unicode if necessary, but the syntax varies depending on which version of EF you are using and how you are configuring your mappings.
Also, as #Steve Py mentioned, you don't need to do ToLower on your strings because most SQL queries are case insensitive.
If you are splitting the name into parts and want to search for a match on the parts then you are joining collections and when I'm joining collections and I can't use navigation properties I usually opt for query syntax:
var query = from person in this.context.Person
from name in lsNames
where person.Name.StartsWith(name) || person.Aliases.Any(a => a.Name.StartsWith(name))
select person;
return query.ToList()
This may not be the most efficient search because it is using wildcards and you may want an exact match.
If you are doing that then you could use StevePy's technique to populate the lsNames array and use a similar query:
string[] lsNames = {combination1, combination2)
var query = from person in this.context.Person
from name in lsNames
where person.Name == name || person.Aliases.Any(a => a.Name == name)
select person;
return query.ToList()
You have to use helper which can build the following predicate:
Expression<Func<Alias, boo>> aliasPredicate = a =>
a.Name.ToLower().Contains(lsNames[0]) ||
a.Name.ToLower().Contains(lsNames[1]) ...
);
For example using LINQKit
string[] lsNames = val.ToLower().Split(' ', ',', '&');
var builder = PredicateBuilder.New<Alias>();
foreach (var n in lsNames)
{
var name = n; // avoid closure capturing of n variable
builder = builder.Or(a => a.Name.ToLower().Contains(name));
}
var personQuery = this.context.Person
.Where(x => x.Alias.AsQueryable().Any(builder));
Note, it will retrieve only persons but not their aliases.
Include(x => x.Alias) may slowdown query. For better performance we have to create more complex solution with client side post processing.
Proposed solution is almost the best when using standard IQueryable extensions and EF Core. Note, there are also database specific features like Full Text Search, which can improve query significally.
I trying to get from Database any name or tag that match any of words from string.
Something like search.
Example:
Query: "Any query that matches"
I have first table with Name, and second with Tags for First Table.
I need something like this, not performance heavy. This will not work because Contains check whole string.
_db.Tag_Table.Where(t => t.First_Table.Name.Contains(query) ||
t.Tag_Table.Value.Contains(query))
.Select(s => s.First_Table).ToList();
Any reasonable solution for this.
I think I found solution, it returns correct results I think.
var queryList = query.Split(' ');
_db.Post.Where(f => queryList.Any(q => f.Title.Contains(q)) || queryList.Any(l => f.Tags.Any(t => t.Tag.Value.Contains(l))));
In every post, for every Query word I'm searching in Post Title or Every Tag Name in Tags Table. I'm returning result if I have Query word in title or in tag.
Thank you for your answers.
It sounds that #Zeeshan Adil 2nd answer is similar.
can you please try this and let me know if it worked in comments:
string[] terms = Query.Split(' ');
string[] Results;
string[] all = (from x in _db.Tag_Table
select x.Name).ToArray();
Results =(from z in all
from w in terms
where z.Contains(w)
select z).Distinct().ToArray();
Edit
also please try this:
string Query = "some value";
string[] terms = Query.Split(' ');
from x in _db.Tag_Table
where terms.Any(val => p.First_Table.Name.Contains(val))
select x;
I've been spending quite a long time trying to make a Linq query with a group by in it, just to return a List for a SelectList with the common Text an Value properties.
I think and still have hope that it does not have to be as difficult as it looks, and i prefer to think that is me and my tired brain sometimes, but honestly i gave up.
I've been able to achieve what i wanted with raw sql but, it's just simple curiosity because I deny to think Linq is that much of a time waster...
So please if someone out there, who knows how to do this in a Linq lambda query, please illuminate my tired brain...
What i got with raw sql and want with linq lambda (it works perfectly, took me 2 minutes and I can populate my MultiSelectFor nicely):
string sql = " select [AlphabeticCode] as 'Value', concat([AlphabeticCode], ' - ', Currency) as Text from [table] " +
" group by AlphabeticCode, Currency; ";
var currency = db.Database
.SqlQuery<Models.myViewModels.LinqToSql.SelectListViewModel>(sql)
.ToList();
model.currency = new SelectList(currency, "Value", "Text");
What i thought i was doing right, made me spend a lonnnng time and ended up giving me a collection inside my text property... not what i wanted at all... (I know is not correct, i tried quite a few more options), but this one kinda made sense to me, please, give a reason to keep working with this weird Linq thing...
var cc = db.table.GroupBy(item => item.Currency)
.Select(group =>
new
{
Value = group.Key,
Text = group.Select(m => m.Currency).Take(1), // here is where i want just ONE string that is the currency name
}
)
.ToList();
Thank you so much to whoever takes some time to read my frustrations...
I am not sure if this is what you need but I think this will make the same query as your SQL
var items = from item in table
group item by new { item.AlphabeticCode, item.Currency } into g
select new { Value = g.Key.AlphabeticCode, Text = g.Key.AlphabeticCode + " - " + g.Key.Currency };
//Fluent or inline
table.
GroupBy(i => new { i.AlphabeticCode, i.Currency }).
Select(g => new { Value = g.Key.AlphabeticCode, Text = g.Key.AlphabeticCode + " - " + g.Key.Currency });
Is it possible to do a LINQ where clause and split a varchar field on a : into a collection and compare this to another collection to see if any values match.
For example, we have a List<string> called AllowedHolders which contains ARR ACC etc.. however the field in the database (which unfortunately we cannot change) is a varchar but has values separated by a colon: i.e. ":ARR:ACC:"
What we would like to do is write a LINQ where clause which can check if any of the AllowedHolders appear in the field. As we wish to restrict the results to only bring back records where the field contains a value in the AllowedHolders collection.
We have done the following where the field only contains a single value
searchResults = searchResults.Where(S => searchParams.AllowedBusinessAreas.Contains(S.SIT_BusinessArea));
But the following will not work because SIT_HolderNames contains values separated by a colon:
searchResults = searchResults.Where(S => searchParams.AllowedHolders.Contains(S.SIT_HolderName)
Any ideas would be much appreciated. If you need me to explain anything further please let me know.
Andy
Use Intersect(), Any() and String.Split().
searchResults = searchResults.Where(s => searchParams.AllowedHolders.Intersect(S.SIT_HolderName.Split(':')).Any());
For example:
":ACC:TEST::ARR:".Split(':') -> string[] { "", "ACC", "TEST", "", "ARR", "" };
You can notice the empty strings, if you don't want to take them into account use String.Split(char[], StringSplitOptions.RemoveEmptyEntries):
":ACC:TEST::ARR:".Split(new char[] {':'}, StringSplitOptions.RemoveEmptyEntries) -> string[] { "ACC", "TEST", "ARR" };
UPDATE
You will have to fetch the data before calling String.Split() using ToList().
searchResults = searchResults.ToList().Where(s => searchParams.AllowedHolders.Intersect(S.SIT_HolderName.Split(':')).Any());
If the data in searchResults is too big what you can do is to fetch only a primary key and the SIT_HolderName.
var keys = searchResults.Select(s => new { Key = s.SIT_PKey, SIT_HolderName = s.SIT_HolderName })
.ToList()
.Where(s => searchParams.AllowedHolders.Intersect(s.SIT_HolderName.Split(':')).Any())
.Select(s => s.Key)
.ToList();
searchResult = searchResults.Where(s => keys.Contains(s.SIT_PKey));
I don't know what can be the performances of the above query. Otherwise, you can try with a Join():
searchResult = searchResults.Join(keys, s => s.SIT_PKey, key => key, (s, key) => s);
Maybe you can use:
searchResults = searchResults.Where(S => searchParams.AllowedHolders
.Any(H => S.SIT_HolderName.Contains(H))
);
or
searchResults = searchResults.Where(S => searchParams.AllowedHolders
.Any(S.SIT_HolderName.Contains)
);
As pointed out by the first comment, this only works if no holder name contains another holder name as a substring. I was implicitly assuming that all your holder names were three-letter strings like ARR and ACC. If this is not the case, consider using (":" + H + ":"), or find a more safe solution.
Edit: Just for completeness, here are two versions with colons prepended and appended:
// OK if some name is contained in another name as a substring
// Requires colon before the first and after the last name
searchResults = searchResults.Where(S => searchParams.AllowedHolders
.Any(H => S.SIT_HolderName.Contains(":" + H + ":"))
);
And:
// OK if some name is contained in another name as a substring
// Ugly checks included to handle cases where no colons are present in the extreme ends
searchResults = searchResults.Where(S => searchParams.AllowedHolders
.Any(H => S.SIT_HolderName.Contains(":" + H + ":") || S.SIT_HolderName.StartsWith(H + ":") || S.SIT_HolderName.EndsWith(":" + H) || S.SIT_HolderName == H)
);
If in the DB column's values the separators are indeed in the format:
:AAA:BBB:CCC:DDD:
and not just (please note the first and last character!)
AAA:BBB:CCC:DDD
then you may perform a LIKE lookup:
select .... from ... where column LIKE '%:BBB:%'
which translates into LINQ:
var idWithColons = ":" + "BBB" + ":";
from ... where .... column.Contains(idWithColons)
for many possible IDS, you'd have to produce:
select .... from ... where column LIKE '%:BBB:%' OR column LIKE '%:DDD:%' OR ..
which translates into LINQ:
var idWithColons = ":" + "BBB" + ":";
var idWithColons2 = ":" + "DDD" + ":";
from ... where .... column.Contains(idWithColons) or column.Contains(idWithColons2)
But that's good only for small number of alternatives. For unknown list of IDs, you can try to rewrite it as a dynamically built filter, but that's not so easy if you are not familiar with Expression<Func<>>.. Anyways, searching via LIKE is not so good idea anyways.. but that's not many other options :/
otherwise, well, that's unpretty.. you could probably prepare a scalar-valued function on the sql server and register it somehow in your linq provider, but I dont think it's worth it..
EDIT:
building where-clause dynamically is explained ie. here http://www.albahari.com/nutshell/predicatebuilder.aspx - look for the PredicateBuilder. The builder is actually generic and will be directly usable, but you still will have to write the small loop that concatenates OR-LIKE by yourself. I think the article is well written enough, drop me anote if you find any problems. except for the performance. LIKE %% is not fast.