Contains any word of string - c#

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;

Related

Linq-to-SQL - how to concatenate two fields in a sub select

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

Searching any word using Linq

In my winform app I have a form where user can search for product by typing any text in search field. For example the product description might be stored as "Adidas aftershave 100Ml" . But user can type any thing like 100 Ml Aftershave. So I would like to query using linq to get all records where description contains any of these word(100 Ml Aftershave).
So far I have done something like this:
List<string>txtList = txtSearchTerm.Text.Split(' ').ToList();
return dbContext.productRepo.Where(t => txtList.Any(b =>
t.ProductDescription.StartsWith(b)
|| t.ProductDescription.EndsWith(b)
|| t.ProductDescription.Contains(b)
)).Select(x => x.ProductDescription).ToList();
Any other way of achieving this in better way ,making it more quick or any other improvement.
Thanks
Well, you are testing if the description starts, ends and contains something. The first two are already redundant because Contains "contains" them:
var matchingDescriptions = dbContext.productRepo
.Where(x => txtList.Any(x.ProductDescription.Contains))
.Select(x => x.ProductDescription));
Another easy optimization(for Linq-To-Objects), I would order the words by length first:
var txtList = txtSearchTerm.Text.Split(' ').OrderBy(s => s.Length).ToList()
One thing you can straightaway improve is to remove StartsWith and EndsWith , Becuase you are already doing t.ProductDescription.Contains(b) .
List<string>txtList = txtSearchTerm.Text.Split(' ').ToList();
return dbContext.productRepo.Where(t => txtList.Any(b =>
t.ProductDescription.Contains(b)
)).Select(x => x.ProductDescription).ToList();

LINQ - Comparing a List of strings against a colon separated field

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.

How to select distinct values from DB separated by comma?

I have a front end including 2 columns, Keywords1 and keywords2 in data base they goes in a single field called keywords (separated by ,). Now I have a search screen which have a Keywords as auto complete text box, now in order populate it I need to get single values from DB, so I have something like,
Keywords
A
A
A,B
B,C
C,E
D,K
Now in order to populate them as a single listItem I need something like.
Keywords
A
B
C
D
k
So that front end doesn't contains and duplicate in it. I am not much expert in SQL, One way I know is just to get the distinct values from DB with like %entered keywords% and the use LINQ to separate them by comma and then get the distinct values. But that would be a lengthy path.
Any suggestion would be highly appreciated.
Thanks in advance.
Maybe a bit late, but an alternative answer that ends up with distinct keywords:
List<string> yourKeywords= new List<string>(new string[] { "A,B,C", "C","B","B,C" });
var splitted = yourKeywords
.SelectMany(item => item.Split(','))
.Distinct();
This will not work straight against the DB though. you would have to read the DB contents into memory before doing the SelectMany, since Split has not equivalent in SQL. It would then look like
var splitted = db.Keywords
.AsEnumerable()
.SelectMany(item => item.Split(','))
.Distinct();
Getting them by using string split and Linq group by
List<string> yourKeywords= new List<string>(new string[] { "A,B,C", "C","B","B,C" });
List<string> splitted = new List<string>();
yourKeywords.ForEach(x => splitted.AddRange(x.Split(',')));
var t = splitted.GroupBy(x => x);

Implement Linq search query for n number of keywords and AND / OR option

I have a textbox that takes free text as input for a search and I have a LINQ query that I want to extend with this type of search.
The input could be something like "big blue car" and that should result in a query that searches for titles that contain all these words.
There is also an option to switch to "any word" instead of "all words".
What is the best/easiest way to add this to my LINQ query?
The query now looks like
from b in books
where b.InStore == true && b.Price > 10 && title.Contains()...at this point i want to add the text search.
select b
I strongly reccommend you to do it with two queries!
But take a look at this, isn't it cool?
var searchAll = true;
var words = List<string>{"big", "blue", "car"};
from b in books
where (...) (searchAll && words.All(x => title.contains(x))) ||
(!searchAll && words.Any(x => title.Contains(x)))
select b
But you really should make it with two different queries.
I would first split the query and the title into words, and then check for containment. A rough cut is
string[] queryParts = query.Split(' ');
books.Where(b => b.InStore)
.Where(b => b.Price > 10)
.Where(b => queryParts.Any(part => b.Title.Split(' ').Contains(part)))
for the any query, and
string[] queryParts = query.Split(' ');
books.Where(b => b.InStore)
.Where(b => b.Price > 10)
.Where(b => queryParts.All(part => b.Title.Split(' ').Contains(part)))
for the all query.
We have to split the title into words because the default String.Contains method looks for any substring match, which means that
"ABC DEF".Contains("A")
returns true, even though for this purpose we don't want it to.
Note that both these solutions assume that words are always delimited by spaces, which isn't true in general. Your user could type tabs in between words, use quotes to delimit groups of words (e.g. "New York"), and so on.

Categories