VB.NET: LINQ Query on DataTable - WHERE Id >=0 - database

Hey people,
I am bugfixing an application for some friends of mine. They are using VB.NET and Windows Forms, while I am in team C#/WPF/ASP. My knowledge about LINQ is also very limited even if its all .NET. I hope you can help me.
The situation
There are two combos on the form.
They are getting their data from the same table status.
The order of the status objects depends on the id-column.
The second combo shall only contain status having id >= combo1.id. So that the user can only select status in the second combo that are "greater-equal" than the status in the first combo.
The smallest status.id is 3.
The code
(please ignore the useless back and forward casting of index etc. I wanted to show you the code as I revieved it.)
Dim index As String = cmbStatusFrom.SelectedValue.ToString()
If index = "0" Then
index = "1"
End If
Dim query As IEnumerable(Of DataRow) = _
From status In ContextDataTable.AsEnumerable() _
Where status.Field(Of Integer)("Id") >= Integer.Parse(index) _
Select status
The problem
Without
If index = "0" Then
index = "1"
End If
the resulting datatable contains one more row than it should.
(0 means that the empty-item has been selected in the first combo) The row has id = 0 (like the empty-row)
Again, the smallest existing id is 3. So it should not make any difference if one uses >=0, 1, 2 or 3. But it seemingly does. Or more likely, there is some really stupid mistake in the code and which I just can't find.
Anyone has got an idea what the problem is?
I do appreciate any help. :-)
greetz steven

I'd like to help, but I'm not quite sure I understand your question. Given the information you have here, that code should work. The only unknown is the actual datatype of the value of the combo, but unless the .Field(Of T) is throwing an InvalidCastException, you should be good there. I wrote the following code and threw it into a unit test project, and everything came out good . . . at least with regard to the length of the resulting set of data.
Code
<TestCase(0, ExpectedResult:=4)>
<TestCase(1, ExpectedResult:=4)>
<TestCase(2, ExpectedResult:=3)>
<TestCase(3, ExpectedResult:=2)>
<TestCase(4, ExpectedResult:=1)>
<TestCase(5, ExpectedResult:=0)>
Public Function tmptest(ByVal selected As Integer) As Integer
Dim d As New DataTable
d.Columns.Add(New DataColumn("id", GetType(Integer)))
For j = 0 To 5
Dim r = d.NewRow()
r("id") = j
d.Rows.Add(r)
Next
If selected = 0 Then selected = 1
Dim query = From status In d.AsEnumerable()
Where status.Field(Of Integer)("id") > selected
Select status
Return query.Count
End Function
Results

Related

Populate a Multi-column Combobox with a 2D array on Access

I tried to follow this method:
ComboBox1.ColumnCount = 2
Dim Films(1 To 5, 1 To 2) As String
Dim i As Integer, j As Integer
Films(1, 1) = "Lord of the Rings"
Films(2, 1) = "Speed"
Films(3, 1) = "Star Wars"
Films(4, 1) = "The Godfather"
Films(5, 1) = "Pulp Fiction"
Films(1, 2) = "Adventure"
Films(2, 2) = "Action"
Films(3, 2) = "Sci-Fi"
Films(4, 2) = "Crime"
Films(5, 2) = "Drama"
ComboBox1.List = Films
source
But the .List property does not work on Access. Any ideas ?
As June7 said, use the ComboBox.AddItem() method in a loop. For your purposes, the ComboBox must not be bound to a data source: It's Row Source Type property should be set to "Value List". To add a multi-column string to a ComboBox row, use a semicolon to delimit the the columns. For example:
ComboBox1.AddItem (Films(1, 1) & ";" & Films(1, 2))
or
Dim rowStr As String
rowStr = Films(1, 1) & ";" & Films(1, 2)
ComboBox1.AddItem (rowStr)
AddItem() automatically appends the row to the end of the ComboBox's list, if you do not specify a row index parameter. For more info, see ComboBox.AddItem method at Office Dev Center.
Screenshot: VBA Demonstration Image
A "Form" in Access is not the same kind of element/object as a "UserForm" is in Excel where your "source" link points to (https://www.excel-easy.com/vba/examples/multicolumn-combo-box.html).
In Access it would be a good idea to get the information into your Combo Box (or List Box) from either a table or a query. You can of course code it with VBA, but then you might find yourself adding/editing a hole lot of VBA here and there, as in Access it all goes more naturally by using SQL and the database engine.
This is a larger topic, but basically you should probably have different tables for "Films" and for "Categories"
Table1:
Table2:
Then you should define the relationships since most likely there are different amount of films in your database than there are categories. Saying that we would like to avoid a situation that you would have to add another movie, let's say "Die hard" into your movie list. That would probably fall into the category "Action". In the database we do not want to repeat ourselves. Just we will, by ID, refer to categoryID by it's value.
So, having done that you need to create a form in Access. Create maybe a query that will get the values for you:
After this you can define the source for the combo e.g. by using wizard:
So this way you can maintain each of the lists separately in their own tables.
Here is the query that got created:
On the Data tab you can decide which bound column to use relative to datasource.
On the Format tab you can adjust the widths of the columns in your combobox. Use 0 length to hide a column.
This way no VBA code is needed.
If needed it is also possible to create or edit the queries with VBA but that is another story.
Hope this helps.

VBA to paste only certain values of cell from one sheet to another

Can some one help me with the below code, what I am looking for is, from sheet "Form" certain values of cells mentioned in 2 sets of Array.
1st set of Array should get copied to sheet "Tracker" C3 onward and second set of array from next cell after the 1set of array ends say EF3 onwards.
whereas now first sett is its pasting from A3 and second from A4. Please let me know in case of any question.
Following is the code which I am using now:
Sub AddEntry()
Dim LR As Long, i As Long, cls
Dim LR2 As Long, j As Long, cls2
cls = Array("C2", "C3", "G2", "G3", "C5", "C6", "C7", "C8", "C9", "C10", "C11", "C12", "C13", "A17", "C17", "D17", "F17", "G17", "H17", "A18", "C18", "D18", "F18", "G18", "H18", "A19", "C19", "D19", "F19", "G19", "H19", "A20", "C20", "D20", "F20", "G20", "H20", "A21", "C21", "D21", "F21", "G21", "H21", "A25", "B25", "C25", "D25", "E25", "F25", "G25", "H25", "A26", "B26", "C26", "D26", "E26", "F26", "G26", "H26", "A27", "B27", "C27", "D27", "E27", "F27", "G27", "H27", "A28", "B28", "C28", "D28", "E28", "F28", "G28", "H28", "A32", "C32", "E32", "G32", "H32", "A33", "C33", "E33", "G33", "H33", "A34", "C34", "E34", "G34", "H34", "A35", "C35", "E35", "G35", "H35", "A39", "D39", "F39", "A40", "D40", "F40", "A41", "D41", "F41", "A45", "C45", "E45", "G45", "A46", "C46", "E46", "G46", "A47", "C47", "E47", "G47", "D51", "D52", "D53", "D54", "D55", "D56", "D57", "D58", "D59", "D60", "D61", "D62", "D63", "D64", "D65", "D66", "D67")
With Sheets("Tracker")
LR = WorksheetFunction.Max(3, .Range("C" & Rows.Count).End(xlUp).Row + 1)
For i = LBound(cls) To UBound(cls)
.Cells(LR, i + 1).Value = Sheets("Form").Range(cls(i)).Value
Next i
End With
cls2 = Array("E51", "E52", "E53", "E54", "E55", "E56", "E57", "E58", "G59", "E60", "E61", "E62", "G63", "E64", "E65", "E66", "E67", "C70", "D70", "E70", "F70", "G70", "H70", "C71", "E71", "G71", "C72", "E72", "G72", "C73", "E73", "G73", "C74", "E74", "G74", "C75", "E75", "G75", "C76", "E76", "G76", "C77", "E77", "G77", "C78", "E78", "G78", "C79", "E79", "G79", "C82", "D82", "E82", "F82", "G82", "H82", "C83", "E83", "G83", "C84", "E84", "G84", "B88", "B89", "B90", "B91", "C88", "C89", "C90", "C91", "D88", "D89", "D90", "D91", "E88", "E89", "E90", "E91", "F88", "F89", "F90", "F91", "G88", "G89", "G90", "G91", "H88", "H89", "H90", "H91")
With Sheets("Tracker")
LR2 = WorksheetFunction.Max(3, .Range("EW" & Rows.Count).End(xlUp).Row + 1)
For j = LBound(cls2) To UBound(cls2)
.Cells(LR, j + 1).Value = Sheets("Form").Range(cls2(j)).Value
Next j
End With
End Sub
Assuming that you want to start cell entries in sheet "Tracker" more to the right, you can add the column number instead of +1 (= column A) and write as follows:
Array 1: assigning cell values starting from column C
.Cells(LR, i + [C1].Column).Value = Sheets("Form").Range(cls(i)).Value
Array 2: assigning cell values starting from column EF
' should be LR2 instead of LR :-)
.Cells(LR2, j + [EF1].Column).Value = Sheets("Form").Range(cls2(j)).Value
Note
[C1].column returns the column number (in any worksheet), e.g. column C Counts 3.
I took a look at your file; the first thing I did was flip through the VBA & try to compile it -- which incidentally, I would recommended to anyone as a first step with a downloaded XLSM. (I haven't seen a malicious macro yet and I'd like to keep it that way!)
I can see that this file has been a "work in progress" because there are bits of code here and there that don't compile properly, such as Me statements pointing to a missing userform, and references to mis-named worksheets such as Form (View) instead of View_Form.
Ideally, this project should be moved from Excel to Access. Excel can be used for filling forms and storing data, but if this is potentially going to sizable, you're best off to use "the right tool for the job". Duplicating your form(s) into Access forms instantly removes the need to copy certain cells to certain sheets, not to mention ease of validation, reporting, security, and unlimited room for expansion plus ease of moving data between Excel, Access, Outlook, etc.
(You even called the spreadsheet a database in one spot!) If your concern is that you're unfamiliar with Access, if you designed this workbook, migration to Access will be a breeze once you figure out the basics of table and form design.
Even Outlook has some pretty nifty form capabilities which can autopopulate the data table when an emailed form is received.
If you need to stay in Excel, how about a User Form instead of the sheet-based form? I too often see people forgetting about Office's built-in features and starting from scratch. That being said, I've been a user of MS Office for 25 years and have never used an Excel User Form. When I think "form", I think MS Access.
Another option, if you want to stay with the worksheet-based form, instead of listing all the cells in the array etc, a minor redesign could make it simpler. One way would be to have a hidden row on the form tab so you have a single uninterrupted line of all the data you need to store. For example, you could hide row 1 and 2, make row 1 the headings like Sourced Processed Year Address etc. and then row 2 could be an "interim" place to store the data, so A2 formula is =C2, B2 is =C3', B3 is=C5` etc.
Finally another sneaky option could be to add hidden comments in each cell that has data that needs to be saved, and then when the form is complete, loop through all the cells looking for comments, and each comment would contain a title or cell reference indicating where that cell's data needs to go.
The destination should be a very straightforward table Use as many columns as you need, but it's not a place for formatting or formulas. (Think database!)
For example, C2 (Sourced By) could have a hidden comment like "Tracker:C" then when the form is filled, you could parse the comments and move the data dynamically (instead of hardcoding 250 cell addresses!) with something like:
Option Explicit
Sub moveData() 'untested; example only
Dim cell As Variant, nextBlankRow As Integer
Dim comm As String, sht As String, col As String
nextBlankRow = 5 'calculate this somehow
'loop through cells with comments
For Each cell In ActiveSheet.Cells.SpecialCells(xlCellTypeComments)
If cell.Comment.Text <> "" Then
'get comment
comm = cell.Comment.Text
'extract location for data like "Sheetname:Columnletter"
sht = Left(comm, InStr(comm, ":") - 1)
col = Right(comm, Len(comm) - InStr(comm, ":"))
'populate correct location with data
Sheets(sht).Range(col & nextBlankRow).Value = cell.Value
End If
Next cell
End Sub
As with anything in Excel (or Office in General) there are a dozen ways you could accomplish the same task. Opt for the ones that don't involve repeating the same code over and over, nor hardcoded data. Planning for future (unexpected) growth is very important, as is debugging as-you-go, which is my last suggestion:
Option Explicit
at the top of every module, and Alt+DLcompile often, removing or commenting-out unused code.
Bottom line, best bet: Access, Excel, Outlook all have form capabilities built in. use a form for a form and you'll save yourself a headache now and later.
Hopefully this gives you some ideas.
Good Luck!

Array elements disappearing / not loading

I have no idea what is going on here and it's a little bizzare.
I'm adapting a VBA macro into a VB.net project, and I'm experiencing what I would describe as some extreemly unusual behavior of a method I'm using to pass data around in VB.net. Here's the set up...
I have, for indexing reasons, a collection that consists of all open orders:
Public allOpenOrders As New Collection
Within this collection, I store other collections, indexed by account number, that each contain information about each open order in an array that is three elements long. Here is how I'm populating it:
openOrderData(0) = some information
openOrderData(1) = some information
openOrderData(2) = some information
SyncLock allOpenOrders
If allOpenOrders.Contains(accountNumber) Then
'Already in the collection...
accountOpenOrders = allOpenOrders(accountNumber)
accountOpenOrders.Add(openOrderData)
Else
'Not already in collection
accountOpenOrders = New Collection
accountOpenOrders.Add(openOrderData)
allOpenOrders.Add(accountOpenOrders, AccountNumber)
End If
End SyncLock
Here's the thing, if I place a stop after end synclock and check the collection, I can clearly see that the array with all data is there, plain as day. However, when I move on in my code (this is occuring in another thread after the preceeding code has executed) to retrieve it and write it to a workbook...
If allOpenOrders.Contains(accountNumber) Then
accountOpenOrders = allOpenOrders(accountNumber)
For each openOrderArray In accountOpenOrders
OutputSheet.Cells(1, 1).value = accountNumber
For counter = 0 to 2
OutputSheet.Cells(1, counter + 2).value = openOrderArray(counter)
Next counter
Next openOrderArray
End If
I get the first element of the array in column B, but C and D are blank. Even more puzzling, if I put a stop right after the allOpenOrders.Contains line I can look at the collection and the last two elements of the array are now blank. Most puzzling of all, they aren't just blank, they are blanks, a number of blanks equal in length to the original field I recorded in that element of the array?!
Any ideas are appreciated. I can tell you I'm using the same type of method to load other data in this workbook with no problems. These are also the only instances in which the allOpenOrders collection is touched... I'm so confused by these results.

What is the best way to give last elements/items of DB table

In my VB code I am reading all the news from database, there are hundreds of them:
For Each lnews In ltable.Rows
Dim litem = lcontent
litem = litem.Replace("%Headline%", _
Web.HttpUtility.HtmlEncode(lnews("Headline")))
Next
I need to get just 3 latest ones. What is the best way to do it?
For i As Integer = 0 To 2
Dim s = ltable.Rows(i)
Next i
I'm not sure about definition of "the best way" here, but you can get last 3 rows from DataTable using LINQ like this :
For Each s In ltable.AsEnumerable().Skip(ltable.Rows.Count() - 3)
's is a DataRow here
Next
or without LINQ :
Dim count = ltable.Rows.Count()
For i As Integer = count - 4 To count - 1
Dim s = ltable.Rows(i)
Next i
You should probably avoid returning more rows than necessary from your database wherever possible, there is no point in returning 100+ rows of data if you only require the use of the top 3 as it will just increase the query time as you get more and more headlines in your database.
If you do need them all to be returned but just require the first three (or last three) then you can use LINQ as already posted by har07.

Search database with textbox

First of all I am working at VB 2012.
I have problem with searching my database. It goes so slow, actually filling the ListView is what bothers me.
I have a text box with TextChange event. Its instant search. So when I'm starting to write in that text box it's starts to filter the database and filling the data in the ListView.
This is the code in text box and the Load procedure
Private Sub txtID_TextChanged(sender As Object, e As EventArgs) Handles txtID.TextChanged
Load("SELECT * FROM table WHERE id LIKE '" & txtID.Text & "%'")
End Sub
Private Sub Load(ByVal strQ As String)
List.Items.Clear()
cmd = New SqlClient.SqlCommand(strQ, con)
dr = cmd.ExecuteReader()
If dr.HasRows = True Then
While dr.Read
Dim X As ListViewItem
X = List.Items.Add(dr(0))
X.SubItems.Add(dr(2))
X.SubItems.Add(dr(3))
X.SubItems.Add(dr(4))
X.SubItems.Add(dr(1))
X.SubItems.Add(dr(5))
End While
End If
End Sub
So, every time I hit a letter it calls the load procedure.
And I have so much data and it goes so slow. Can you help me somehow ? Is there any solution ?
I don't know how you're ever going to possibly speed that up. Connecting and querying the database is a lot of overhead, especially compared to the speed of pressing a key or typing a word. There's just not a way to do this without severely affecting the user who's typing.
What I suggest instead is that you wait for the user to tell you they're done typing before you bother making a search. If you're trying to do fancy auto-completion stuff you're going to need to cache the data a lot closer to the app than the database.
You need to create a class to hold the search results, like this:
Public Class SearchResult
Private _propID As String
Public Property ID() As String
Get
Return _propID
End Get
Set
_propID = Value
End Set
End Property
Private _propName As String
Public Property PropName() As String
Get
Return _propName
End Get
Set
_propName = Value
End Set
End Property
...
End Class
Now you query the database to get all the results to display in the list view, storing it in a List(Of SearchResult), like this:
Private Function Load(ByVal strQ As String) As List(Of SearchResult)
Dim ListOfResults = New List(Of SearchResult)
cmd = New SqlClient.SqlCommand(strQ, con)
dr = cmd.ExecuteReader()
If dr.HasRows = True Then
While dr.Read
Dim X As New SearchResult()
X.PropID = dr(0)
X.PropName = dr(1)
...
End While
End If
End Sub
You can call this code like this:
Dim AllSearchResults = Load("SELECT * FROM table WHERE id LIKE '" & txtID.Text & "%'")
Now when you want to do a search you can apply the following LINQ against your cached list of everything (AllSearchResults), like this:
Public Function DoSearch(searchText As String) As List(Of SearchResult)
Return From s In AllSearchResults Where s.PropID.Contains(searchText) Select c
End Function
Finally, you can call this LINQ filtering on each key press by the user, like this:
Private Sub txtID_TextChanged(sender As Object, e As EventArgs) Handles txtID.TextChanged
DoSearch(txtID.Text)
End Sub
One way to cut the overhead down by a fair degree to to wait to issue the query until the length of the text entered is 3 (or better yet 5) characters. It is very, very unlikely that all customers (or whatever these are) starting with 'S' is going to be meaningful or helpful to anyone except the one person on rare occasions looking for "Sab....". People HAVE to be typing in the 2nd and 3rd char before the first query is complete and displayed!
To try to make a really bad idea less bad, I'd look into a way to issue the query ONCE (on the first character if I really, really has to), then filter those results down on subsequent keystrokes. (ala Aaron's "caching the data a lot closer to the app").
The next thing is Select *. I dunno whats in the table, but do you REALLY need every column? This appears to be some sort of pick list, do you really need 6 fields to provide the user the information needed to make a selection? If there are more than 6 columns in the table, immediately narrow the query to the 6 used in the listview. Once they make their choice you can go back and get exactly what you need by ID or whatever.
I'd personally use a faster control but thats subjective.
ALL databases eventually acquire dormant data. Customers (or whatever) that were one time shoppers and never return - do they need to be in the list? If there is a column somewhere for lastorderdate or lastupdateddate construct your query to pick those active in the last XX months (and if not, see if you can add one because the issue is not going to get better as the database gets larger!). Then a checkbox for the user to widen the range as needed, like "See All" or something. Users are not likely to balk at the idea if it speeds things up the other 80% of the time.
...those are just off the top of my head.

Resources