Using SQL Wildcards with LINQ - sql

I have a question about using LINQ to access a Database and trying to make use of it's version of accessing the LIKE comparison operator.
I know that LINQ has .Contains(), .StartsWith(), and .EndsWith() as comparison methods. However I am wondering if there is a way to programatically imcorporate SQL Wildcards into a LINQ statement without explicitly using these query operators. Let me explain my situation.
I am writing a program that accesses a database, and part of the program is a search window which the user can use to help them find specific database data. I would like to try and incorporate SQL Wildcards into the textbox fields for these search pages.
For example if a user enters the input 17% I'd want the program to check for anything in that specific column that starts with a 17. The same is true with %17 and 17 where I'd want it to search for columns that end with, and contain the values.
Currently, this is the code I have for my search method:
Public Function Data_Search(sData As List(Of String), DB As CustomDataContext) As DataGridView
Dim gridData As New DataGridView
Dim query = From p In DB.Parts
Select p.Part_Number, p.Part_Description, p.Supplier.Supplier_Name
for i = 0 To sData.Count - 1
If Not sData(i).ToString() = "" Then
Select Case i
Case 0
Dim partNum As String = sData(i).ToString()
query = query.Where(Function(x) x.Part_Number.Contains(partNum))
Case 1
Dim description As String = sData(i).ToString()
query = query.Where(Function(x) x.Part_Description.Contains(description))
Case 2
Dim supp As String = sData(i).ToString()
query = query.Where(Function(x) x.Supplier_Name.Contains(supp))
End Select
End If
Next
gridData.DataSource = query.ToList()
Return gridData
End Function
So right now I am trying to see if there is a way for me to modify the code in a way that doesn't essentially involve me putting a substring search into each Case section to determine if I should be using StartsWith(), Contains(), or EndsWith.
Thanks for your time.

If you are using LINQ to SQL, and you are talking to Microsoft SQL Server, then you can use SQLMethods.Like to implement SQL LIKE:
query = query.Where(Function(x) SQLMethods.Like(x.Part_Number, partNum))

Related

Why does my SQL query return two different results?

I'm using SQL in MS Access to retrieve data from a table but depending on whether I run a query or use VBA, I get two different results for the same query.
Quite simply, I want the max value from a single column in a table. The trouble is, I think, is that the column datatype is text? The table consists, primarily, of 6 digit numbers interspersed with other formats such as 'xx-xxxxx'.
Firstly, I need to be able to do this in VBA, so I have this:
Private Function Create() As String
Dim rs As New ADODB.Recordset
Dim cn As New ADODB.Connection
Dim strSQL As String
Set cn = CurrentProject.Connection
cn.CursorLocation = adUseClient
strSQL = "SELECT Max(par.PN) AS PN FROM par WHERE ((PN Between CLng('100000') And CLng('999999')) And (PN Not Like '##-#####'));"
Set rs = cn.Execute(strSQL)
If (rs.RecordCount > 0) Then
'Create = rs("PN")
Debug.Print rs("PN")
End If
End Function
The result is 10-0000 so my first though is that the query is wrong so I head off over to the query builder and enter:
SELECT Max(par.PN) AS PN
FROM par
WHERE ((PN Between CLng('100000') And CLng('999999')) And (PN Not Like '##-#####'));
which returns 745864 when I run it. So from this I can see there is something awry here.
Now, the other thing that strikes me is that I don't think I should need And (PN Not Like '##-#####') in order to get a six digit number as I would have though WHERE ((PN Between CLng('100000') And CLng('999999')) would have been sufficient...
I have a suspicion that this is because the numbers are stored as text and because of this the between function will not work on text - if this is indeed true, I would have further expected that the CLng function would have converted successfully to number in order for this query to work.
In any case, I need to be able to get the latest six digit number using vba. Can anyone shed any light on where the problem lies?
Thanks
ADO SQL uses the ANSI 92 version of the LIKE operator (allowed wildcards: _ for a single character, % for multiple characters).
MS Access and DAO use a different form of LIKE operator (see this MSDN page for the allowed wildcards)
If you want to use such a comparison, I recommend you switch to DAO (if you don't have a specific reason to use ADO)
An alternative approach (and the one that would have my preference) would be casting PN to an actual number:
SELECT Max(CLng(Replace(par.PN, "-", ""))) AS PN
FROM par
WHERE CLng(Replace(par.PN, "-", "")) Between 100000 And 999999

Compare Two Linq Queries

I have the following two queries that take information from the same table
I need to compare the two tables and find all the information that's value is different. Is there any way to do this without having to make a loop?
Dim housepress = (From press In db.PressInfo
Where press.PressName = pressname And press.CustomerID = "House"
Select press).ToList()
Dim curpress = (From press In db.PressInfo
Where press.PressName = pressname And press.CustomerID = Customername
Select press)
I tried using curpress.Except but I get an error that "Local sequence cannot be used in Linq to SQL

Retrieving the Query used for a OleDBCommand

I'm currently using the following VB code to make a query against an Access Database, I would like to know is it possible to obtain what the SELECT statement that is being run and send that output to the console.
Dim QuestionConnectionQuery = New OleDb.OleDbCommand("SELECT Questions.QuestionID FROM Questions WHERE Questions.QuestionDifficulty=[X] AND ( Questions.LastDateRevealed Is Null OR Questions.LastDateRevealed < DateAdd('d',-2,Date() ) AND Questions.LastUsedKey NOT LIKE ""[Y]"" );", QuestionConnection)
QuestionConnectionQuery.Parameters.AddWithValue("X", questionDifficulty.ToString)
QuestionConnectionQuery.Parameters.AddWithValue("Y", strDatabaseKey)
Right now when I try to use: Console.WriteLine("Query: " & QuestionConnectionQuery.ToString)
I only get this:
Loop Question #1
Query: System.Data.OleDb.OleDbCommand
The short version comes down to this:
QuestionConnectionQuery.ToString
The QuestionConnectionQuery object is much more than just the text of your command. It's also the parameters, execution type, a timeout, and a number of other things. If you want the command text, ask for it:
QuestionConnectionQuery.CommandText
But that's only the first issue here.
Right now, your parameters are not defined correctly, so this query will never succeed. OleDb uses ? as the parameter placeholder. Then the order in which you add the parameters to the collection has to match the order in which the placeholder shows in the query. The code in your question just has X and Y directly for parameter placeholders. You want to do this:
Dim QuestionConnectionQuery AS New OleDb.OleDbCommand("SELECT Questions.QuestionID FROM Questions WHERE Questions.QuestionDifficulty= ? AND ( Questions.LastDateRevealed Is Null OR Questions.LastDateRevealed < DateAdd('d',-2, Date() ) AND Questions.LastUsedKey NOT LIKE ? );", QuestionConnection)
QuestionConnectionQuery.Parameters.Add("?", OleDbType.Integer).Value = questionDifficulty
QuestionConnectionQuery.Parameters.Add("?", OleDbType.VarChar, 20).Value = strDatabaseKey
I had to guess at the type and lengths of your parameters. Adjust that to match the actual types and lengths of the columns in your database.
Once you have made these fixes, this next thing to understand is that the completed query never exists. The whole point of parameterized queries is parameter data is never substituted directly into the sql command text, not even by the database engine. This keeps user data separated from the command and prevents any possibility of sql injection attacks.
While I'm here, you may also want to examine the WHERE conditions in your query. The WHERE clause currently looks like this:
WHERE A AND ( B OR C AND D )
Whenever you see an AND next to an OR like that, within the same parenthetical section, I have to stop and ask if that's what is really intended, or whether you should instead close the parentheses before the final AND condition:
WHERE A AND (B OR C) AND D
This will fetch the command text and swap in the parameter values. It isnt necessarily valid SQL, the NET Provider objects haven't escaped things yet, but you can see what the values are and what the order is for debugging:
Function GetFullCommandSQL(cmd As Data.Common.DbCommand) As String
Dim sql = cmd.CommandText
For Each p As Data.Common.DbParameter In cmd.Parameters
If sql.Contains(p.ParameterName) AndAlso p.Value IsNot Nothing Then
If p.Value.GetType Is GetType(String) Then
sql = sql.Replace(p.ParameterName,
String.Format("'{0}'", p.Value.ToString))
Else
sql = sql.Replace(p.ParameterName, p.Value.ToString)
End If
End If
Next
Return sql
End Function
Given the following SQL:
Dim sql = "INSERT INTO Demo (`Name`, StartDate, HP, Active) VALUES (#name, #start, #hp, #act)"
After parameters are supplied, you can get back this:
INSERT INTO Demo (`Name`, StartDate, HP, Active) VALUES ('johnny', 2/11/2010 12:00:00 AM, 6, True)
It would need to be modified to work with OleDB '?' type parameter placeholders. But it will work if the DbCommand object was created by an OleDBCOmmandBuilder, since it uses "#pN" internally.
To get or set the text of the command that will be run, use the CommandText property.
To print the results, you need to actually execute the query. Call its ExecuteReader method to get an OleDbDataReader. You can use that to iterate over the rows.
Dim reader = QuestionConnectionQuery.ExecuteReader()
While reader.Read
Console.WriteLine(reader.GetValue(0))
End While
reader.Close()
If you know the data type of the column(s) ahead of time, you can use the type-specific methods like GetInt32. If you have multiple columns, change the 0 in this example to the zero-based index of the column you want.

vb.net DAL Specify columns returned

I have a Data Access Layer class that has a method (GetPeople) that will retrieve records from a SQL Server table (people). This table has more than 20 fields, including varbinary type.
Right now, SQL query is something like
SELECT * FROM people
From my BLL class, I will call DAL.GetPeople(), which will return all columns.
What would be the best way to specify which columns to return, so I could improve performance? For example, sometimes I would like to return all fields, other times, just one or two.
UPDATE
To explain it better:
In DAL I have a method GetPeople() which calls a SQL Server function GetPeople.
In BLL I have a method GetPeople() which calls DAL.GetPeople(), after doing some business logic.
In my presentation layer, I call BLL.GetPeople().
This is working, but on SQL function, I have "SELECT * FROM people". Sometimes I would like to retrieve only one column (eg. name) from table, but in this case all columns are returned, which I think is affects performance.
So, I would like to have a kind of dynamic SELECT query on this SQL Server function, whose columns returned would depend on how I call the function...
I think you are after something like this where you can pass in a comma-seperated list of column names
Private Function GenerateQuery(ByVal columnNames As String) As String
' columnNames in the following format 'column1,column2,column3'
Dim lstColumnNames As String() = Split(columnNames, ",")
Dim strSQL As New StringBuilder
strSQL.Append("SELECT ")
For intColNumber As Integer = 0 To lstColumnNames.GetUpperBound(0)
strSQL.Append("[")
strSQL.Append(lstColumnNames(intColNumber))
strSQL.Append("]")
If intColNumber < lstColumnNames.GetUpperBound(0) Then
strSQL.Append(", ")
End If
Next
strSQL.Append(" FROM People ")
Return strSQL.ToString
End Function
You can use it like this: SqlCommand.CommandText = GenerateQuery("column1,column2,column3")
The column names are wrapped in [] symbols so you don't have to worry about reserved words causing the database to error.
Change your SQL-query to something like
SELECT column1, column2, column3 FROM people;
EDIT:
What you are going to need to do is create function that will put your SQL string together for you. When i did this before, I had all of the available fields in a checked-list control, and if i wanted them pulled, I checked them. The checked items were then put through the function to assemble the string. It should be pretty simple since there are not any joins going on.

Report Builder 3.0 underlying SQL

I have a report builder 3.0 report that has several parameters on it. Specifically, an account number (Defined as char(12) in the database). The database is a vendor supplied database, so I have zero control over the database schema.
My question is when I have a free form parameter for account id, how is that transformed into the query sent to the sql database?
The way I handle these free form fields is that I have a user defined function:
Public Function ConvertStringtoArray(sourceString as String) As string()
Dim arrayOfStrings() As String
Dim emptyString as String = " "
If String.IsNullOrEmpty(sourceString) Then
arrayOfStrings = emptyString.Split(",")
Else
arrayOfStrings = sourceString.Replace(" ", "").Split(",")
End If
return arrayOfStrings
End Function
And the parameter is defined as:
#AcctList = code.ConvertStringToArray(Parameters!AcctList.Value)
The sql query has this in the where clause:
Ac.Account_ID In (#AcctList)
My question is how does it build the In Clause. Will it literally be something like:
Where Ac.Account_ID In (N'Acct1',N'Acct2').
I'm thinking it is, and the reason I think it's important is the query when I am running it in SSMS will run in less than 1 Second if my where clause has Where Ac.Account_ID In ('TGIF').. But it will take 13+ Seconds if I have Where Ac.Account_ID In (N'TGIF'). The total dataset returned is only 917 Rows.
The database I am querying is a 2008 R2 SP2, with the compatibility set to SQL 2008.
You assumption is correct for the predicate of 'where thing in (#parameter)' actually being 'where thing in ('value1', 'value2', etc). Provided that #parameter allows multiple values. However you can tie a parameter to a query as well as using code. You can have a dataset other than your main dataset like a simple 'Select value from values' where the values would be a table of values needed for a parameter choice. This is often much more efficient unless you have to do a string split.