NHibernate advanced search headache - linq

I really believe this is a simple problem, but one of those with a solution that's not obvious to an NHibernate newbie like me...
Here's the deal, I'm conducting my NHibernate-related queries from a data service layer that knows nothing about NHibernate (for separation of concerns). As such, I'm constructing my queries by using LINQ (Sytem.Linq).
I want to search on more than one word. For instance, if someone types in "training excel", then I will search a number of entities and related location entities based on both words.
Here's what my code looks like in my service layer right now:
// We are delimiting by spaces and commas.
string delimiterString = " ,";
char[] delimiter = delimiterString.ToCharArray();
IEnumerable<string> words = searchWords.Split(delimiter, StringSplitOptions.RemoveEmptyEntries);
// Loop through each search word in the collection and apply the "Where" clause to our IQueryable collection:
foreach (string word in words) {
matches = matches.Where(i => i.Subject.Contains(word)
|| i.Background.Contains(word)
|| i.Summary.Contains(word)
|| i.Organization.Contains(word)
|| i.Locations.Any(l => l.Organization.Contains(word))
|| i.Locations.Any(l => l.City.Contains(word))
);
}
Here's the issue... through viewing my application logs and running NHibernate Profiler, I see that the T-SQL query is correctly being constructed. The search works fine with just one search word passed in. However, if 2 or more words are detected, the last word detected is the only one searched on. For instance, if the search term was "training excel", then, as I step through my code above, both words are correctly added in the loop, but the final T-SQL output has "excel" in both logical where groups in the WHERE clause (i.e. WHERE course.Subject like ('%' + 'excel' + '%')...... AND course.Subject like ('%' + 'excel' + '%')......). There should have been "training" in the first group and "excel" in the second group.
It seems like NHibernate is using some sort of query caching for efficiency because the query signature is the same (since we're looping through all the words). Again, I have verified that both words are being used when stepping through my code.
Any ideas??

This must be the common pitfall "access to modified closure". Try
foreach (string word in words)
{
var wordLoopVariable = word;
matches = matches.Where(i => i.Subject.Contains(wordLoopVariable)
|| i.Background.Contains(wordLoopVariable)
|| i.Summary.Contains(wordLoopVariable)
|| i.Organization.Contains(wordLoopVariable)
|| i.Locations.Any(l => l.Organization.Contains(wordLoopVariable))
|| i.Locations.Any(l => l.City.Contains(wordLoopVariable))
);
And do some googling on closures.

Related

How to properly perform like queries with Quickbase

I am working with quicktable queries and everything seems to be fine.
Now I want to perform queries using like operators. For instance in PHP I can do something like:
$data ='content to search';
$stmt = $db->prepare('SELECT * FROM members where name like :name OR email like :email limit 20');
$stmt->execute(array(
':name' => '%'.$data.'%',
':email' => '%'.$data.'%',
));
Now in quick table, I have tried using CT, EX or HAS parameter etc with OR Operators. Only CT gives nearby result but not exact as per code below.
//Email = 7
//name =8
{
"from": "tableId",
"where": "{7.CT.'nancy#gmail.com'}OR{8.CT.'nancy'}"
}
Is there any way I can obtain a better search with like operators with Quickbase. The documentation here does not cover that.
CT is the closest string comparison operator in Quick Base to LIKE in SQL, but since you can't use wildcards in Quick Base queries you might need to group multiple query strings to achieve the same result. The is also a SW operator that can sometimes come in helpful for comparing parts of a strings.

LINQ query to find items where multiple substrings are searched on one column

Select * from web_data where Title like "%Lawn%" || Title like "%silk%"......and so on.
Lawn, Silk etc are in List<web_data>. So i'm looking for a way to search those substrings in the Title column(discription property). One of them must be contained, then the row should be returned.
I've tried this
query = query
.Where(x => filter.FabricType.Any(f => x.discription.Contains(x.discription)))
.AsQueryable();
It's not working. That linq to sql code returns an error:
Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.
any alternatives?
You can use Contains() or You can also use .StartsWith() or .EndsWith().
collListItem.Where(x => x.discription.Contains("Lawn") || x.discription.Contains("silk")).ToList();

multiple where statements combined with OR in Activerecord and Ruby

I would like to do a query with activerecord (not rails) with multiple keywords that are contained in a field (so I have to use LIKE) but I don't know in advance how many keywords there will be.
My query looks like this, Word is my model.
query = ['word1','word2'] #could be more
puts "searching for #{query}"
qwords = Word.none
query.each do |qword|
puts qwords.where("word like ?", "%#{qword}%").to_sql
qwords = qwords.where("word like ?", "%#{qword}%")
end
Which gives nothing because the queries are added as AND but I need OR.
searching for ["word1", "word2"]
SELECT "words".* FROM "words" WHERE (word like '%word1%')
SELECT "words".* FROM "words" WHERE (word like '%word1%') AND (word like '%word2%')
#<ActiveRecord::Relation []>
I can't use Word.where(word: query) which uses the sql IN keyword because that only works for exact matches.
Is there a solution that doesn't involves concatenating the whole SQL that is needed ?
query = "word1 word2" #could be more
puts "searching for #{query}"
query_length = query.split.length #calculates number of words in query
Now you can put together the number of SQL queries you need regardless of the number of keywords in your query
Word.where([(['word LIKE ?'] * query_length).join(' OR ')] + (query.split.map {|query| "%#{query}%"}))
This should return
["word LIKE ? OR word LIKE ?", "%word1%", "%word2%"]
for your SQL search
Had forgotten about this question and found a solution myself afterward.
I now do the following. The problem was caused by using the resultset to do my next query on while like this it is on the whole recordset and the results are added.
#qwords = Word.none
$query.each do |qword|
#qwords += Word.where(word: qword)
end

LINQ to SQL to find match in results that have multiple pipe separated values

I have a table that has multiple columns, one of which contains pipe separated values.
I found an answer that's partially what I'm looking for here but that assumes you're searching in one CSV-type list.
What I have is rows of data, one column (called serviceIDs) has data like 2|45|5|6
I want to be able to pass a value into a query something like this:
Select all rows where serviceIDs contains '5'
or
Select all rows where serviceIDs like '%5%'
But obviously neither of those are going to work properly. Is there a way to do this in LINQ?
Unfortunately Split isn't supported in LINQ to SQL. You could do something like the following where you tack on a leading and trailing pipe and then use contains with the leading and trailing pipe. That would solve the issue where finding 45 when searching for 5. Be aware that this could kill your performance because the calculated column value would block index usage in the database. If you store the original values with the leading and trailing pipe, the indexing would be better (but not as good as using a full text search).
var yourvalue = "5";
var expectedResult = "|" + yourvalue + "|";
var resultset = rows.Where(row => ("|" + row.serviceIDs + "|").Contains(expectedResult);
var yourvalue = "5";
var resultset = rows.Where(row => row.serviceIDs.Split('|').Contains(yourvalue));
disclaimer: i never used linq for DB stuff, but something along these lines might work
yourTable.AsEnumerable()
.Select(row => row["serviceIDs"].ToString().Split('|'))
.ToList()
.Where(s => s == 5);

LINQ syntax where string value is not null or empty

I'm trying to do a query like so...
query.Where(x => !string.IsNullOrEmpty(x.PropertyName));
but it fails...
so for now I have implemented the following, which works...
query.Where(x => (x.PropertyName ?? string.Empty) != string.Empty);
is there a better (more native?) way that LINQ handles this?
EDIT
apologize! didn't include the provider... This is using LINQ to SQL
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=367077
Problem Statement
It's possible to write LINQ to SQL that gets all rows that have either null or an empty string in a given field, but it's not possible to use string.IsNullOrEmpty to do it, even though many other string methods map to LINQ to SQL.
Proposed Solution
Allow string.IsNullOrEmpty in a LINQ to SQL where clause so that these two queries have the same result:
var fieldNullOrEmpty =
from item in db.SomeTable
where item.SomeField == null || item.SomeField.Equals(string.Empty)
select item;
var fieldNullOrEmpty2 =
from item in db.SomeTable
where string.IsNullOrEmpty(item.SomeField)
select item;
Other Reading:
1. DevArt
2. Dervalp.com
3. StackOverflow Post
This won't fail on Linq2Objects, but it will fail for Linq2SQL, so I am assuming that you are talking about the SQL provider or something similar.
The reason has to do with the way that the SQL provider handles your lambda expression. It doesn't take it as a function Func<P,T>, but an expression Expression<Func<P,T>>. It takes that expression tree and translates it so an actual SQL statement, which it sends off to the server.
The translator knows how to handle basic operators, but it doesn't know how to handle methods on objects. It doesn't know that IsNullOrEmpty(x) translates to return x == null || x == string.empty. That has to be done explicitly for the translation to SQL to take place.
This will work fine with Linq to Objects. However, some LINQ providers have difficulty running CLR methods as part of the query. This is expecially true of some database providers.
The problem is that the DB providers try to move and compile the LINQ query as a database query, to prevent pulling all of the objects across the wire. This is a good thing, but does occasionally restrict the flexibility in your predicates.
Unfortunately, without checking the provider documentation, it's difficult to always know exactly what will or will not be supported directly in the provider. It looks like your provider allows comparisons, but not the string check. I'd guess that, in your case, this is probably about as good of an approach as you can get. (It's really not that different from the IsNullOrEmpty check, other than creating the "string.Empty" instance for comparison, but that's minor.)
... 12 years ago :) But still, some one may found it helpful:
Often it is good to check white spaces too
query.Where(x => !string.IsNullOrWhiteSpace(x.PropertyName));
it will converted to sql as:
WHERE [x].[PropertyName] IS NOT NULL AND ((LTRIM(RTRIM([x].[PropertyName])) <> N'') OR [x].[PropertyName] IS NULL)
or other way:
query.Where(x => string.Compare(x.PropertyName," ") > 0);
will be converted to sql as:
WHERE [x].[PropertyName] > N' '
If you want to go change the type of the collection from nullable type IEnumerable<T?> to non-null type IEnumerable<T> you can use .OfType<T>().
.OfType<T>() will remove null values and return a list of the type T.
Example: If you have a list of nullable strings: List<string?> you can change the type of the list to string by using OfType<string() as in the below example:
List<string?> nullableStrings = new List<string?> { "test1", null, "test2" };
List<string> strings = nullableStrings.OfType<string>().ToList();
// strings now only contains { "test1", "test2" }
This will result in a list of strings only containing test1 and test2.

Resources