vb.net DAL Specify columns returned - vb.net

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.

Related

Using SQL Wildcards with LINQ

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

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.

Defining an expression or code with lookup RS

I need to migrate a CR report to RS.
I have two Methods, that are basically two queries, one is called "Products" the other one is called "Volume".
Both these datasets have a field called ID.
The idea is to count ProductsID = VolumeID. Can I use a lookup as an expression inside RS that counts when lookup is not equal to Nothing? Or I have to do a code to do that?
EDIT:
Let me add more information.
Crystal Report is managing these in the Data Layer of the web app like this:
DataRow drVol = vols.Rows.Find(new Object[] { id, idEzd });
if (drVol != null)
{
if (drVol["Volume"] != System.DBNull.Value)
cRow.Volume = (isNormalReport ? Convert.ToDecimal(drVol["Volume"]) : Convert.ToDecimal(drVol["Volume"]));
else
cRow.Volume = 0;
dset.Compradores.Rows.Add(cRow);
}
So in this case I verified that RS is giving me an X amount of rows while CR is giving me another amount of rows.
The Idea is to duplicate this on an expression or VB code inside RS.
As you can see, we have to filter the drVol != null, RS is bringing all the rows.
I know a Lookup function can be used in this case as:
Lookup(Fields!id.Value & Fields!idEzd.Value, Fields!id.Value & Fields!idEzd.Value,Fields!Givemethisfield.Value,"Dataset")
But can I use this Lookup expression inside a code to count?
The counter should be something like this (Then I should add the lookup or an if that equals that vlookup, can the code see the two datasets?):
Dim count=0 as integer
Public Function Counter() as Integer
count = count + 1
return count
End Function
Public Function GetCounter () as integer
return count
End Function
Then create a Calculated Field for example "Counter" with the expression
=Code.Counter()
And then make the sum of that calculated field:
=Sum(Fields!Counter.Value)
But is not working, not even the counter, is giving me 0.
Sorry if I made a mistake in the Visual Basic code, I don't program in Visual.
EDIT2:
I saw some workaround on MSDN, something like:
Lookup(Fields!id.Value & Fields!idEzd.Value, Fields!id.Value & Fields!idEzd.Value,Fields!Givemethisfield.Value,"Dataset").Lenght
It makes sense as a lookup is an Array.
The problem is, either if I choose one scope or the other, I still have the problem that some fields can't be seen for example if the fields id and idezd are on Dataset1 and Givemethisfield is on Dataset2, it will either not see the id,idEzd or Givemethisfield.

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.

how to replace text in a multifield value column in access

I've got a tablea such as below, I know its bad design having multifield value column but I'm really looking for a hack right now.
Student | Age | Classes
--------|------|----------
foo | 23 | classone, classtwo, classthree, classfour
bar | 24 | classtwo, classfive, classeight
When I run a simple select query as below, I want the results such a way that even occurrence of classtwo is displayed as class2
select student, classes from tablea;
I tried the replace() function but it doesnt work on multivalued fields >_<
You are in a tough situation and I can't think of a SQL solution for you. I think your best option would be to write a VB function that will take the string of data, parse it out (replacing) the returning you the updated string that you can update your data with.
I can cook up quite a few ways to solve this.
You can explode the mv by using Classes.Value in your query. This will cause one row to appear for each value in the query and thus you now can use replace on that. However, this will result in one separate row for each class.
So use this:
Select student, classes.Value from tablea
Or, for this example:
Select student, replace(classes.Value,"classtwo","class2") as myclass
from tablea
If you want one line, AND ALSO the multi value classes are NOT from another table (else they will be returning ID not text), then then you can use the following trick
Select student, dlookup("Classes","tablea","id = " & [id]) as sclasses
from tablea
The above will return the classes separated by a space as a string if you use dlookup(). So just add replace to the above SQL. I suppose if you want, you could also do replace on the space back to a "," for display.
Last but not least, if this those classes are coming from another table, then the dlookup() idea above will not work. So just simply create a VBA function.
You query becomes:
Select student, strClass([id]) as sclasses from tablea
And in a standard code module you create a public function like this:
Public Function strClass(id As Variant) As String
Dim rst As DAO.Recordset
If IsNull(id) = False Then
Set rst = CurrentDb.OpenRecordset("select Classes.Value from tableA where id = " & id)
Do While rst.EOF = False
If strClass <> "" Then strClass = strClass & ","
strClass = strClass & Replace(rst(0), "classtwo", "class2")
rst.MoveNext
Loop
rst.Close
Set rst = Nothing
End If
End Function
Also, if you sending this out to a report, then you can DUMP ALL of the above ideas, and simply bind the above to a text box on the report and put the ONE replace command around that in the text box control. It is quite likely you going to send this out to a report, but you did ask how to do this in a query, and it might be the wrong question since you can "fix" this issue in the report writer and not modify the data at the query level. I also think the replace() command used in the report writer would likely perform the best. However, the above query can be exported, so it really depends on the final goal here.
So lots of easy ways to do this.