find files then parse the results before displaying the results - vb.net

I'm working on an app for our customer service division. The user will enter a print number, and a mfg date. the app will then do a get files returning results of all files found with that drawing number (multiple revisions). the files are named in this format drawingnumber_rev_pages_6-digit date.pdf.
Once I have the list of that drawing number, I then take a right(string) of 10 characters to strip the 6-digit date off, and do a Date.ParseExact to compare to the user input mfg date. I was grabbing anything prior to that date, and showing them in a listbox. the criteria has now changed, and they want me to only show the file that would pertain to that build date. The problem is, I need the first rev prior to that date. and I don't fully understand the (function) portion of my getfiles statement. So I don't know what to search for to even lookup google results. as I search for getfiles, function isn't mentioned, if I look up orderby... function isn't mentioned, is it linq I need to search under? how would you propose I approach this?
original version example
new version, I can filter result and find everything before date ... but what I want is the highest revision before the mfg. date. return a single result.
current version
thank you all- here's my sample code.
Try
Dim results = Directory.GetFiles(fp, f).
OrderByDescending(Function(x) New FileInfo(x).FullName)
For Each result In results
Dim dp As String = ""
Dim d As Date
dp = Strings.Right(result, 10)
dp = Strings.Left(dp, 6)
d = Date.ParseExact(dp, "MMddyy", New Globalization.CultureInfo("en-us"))
Debug.Print(dp)
Debug.Print(d)
Dim udt As String = ""
Dim ud As Date 'user date
udt = Trim(Replace(txtMfgDate.Text, "/", ""))
If udt.ToString.Length = 0 Then lstFound.Items.Add(result)
ud = Date.ParseExact(udt, "MMddyy", New Globalization.CultureInfo("en-us"))
If d.Date <= ud.Date Then
lstFound.Items.Add(result)
End If
Debug.Print(d)
Debug.Print(ud)
If result <> Nothing Then
End If
Next
Catch x As Exception
MessageBox.Show("Error finding file: " & f)
End Try

LINQ can do everything you need with the right conditions. I also tried to clean up some of the code in other ways. I switched the LINQ expression to query comprehension to so I could use Let.
Try
Dim enusCulture = New Globalization.CultureInfo("en-us") ' consider CultureInfo.CurrentCulture instead?
Dim udt As String = Trim(Replace(txtMfgDate.Text, "/", ""))
Dim ud As Date
If udt.Length > 0 Then ud = Date.ParseExact(udt, "MMddyy", enusCulture) ' user mfg date
' 0071911_a_1_072115.pdf
Dim results = From filename In Directory.GetFiles(fp, f)
Let filedate = Date.ParseExact(filename.Substring(filename.Length - 10, 6), "MMddyy", enusCulture)
Where udt.Length = 0 OrElse filedate.Date < ud.Date
Order By filedate Descending
If udt.Length> 0 Then
results = results.Take(1)
End If
lstFound.Items.AddRange(results)
Catch x As Exception
MessageBox.Show("Error finding file: " & f)
End Try
The Function(x) syntax is a lambda expression, and it is part of LINQ method syntax for the OrderByDescending function, if you want to research that.

Related

Gridview CustomUnboundColumnData Date column with custom strings filter error

I have a column with dates and strings in my table (varchar) named DateColumn and i want to show it as dates to be able to filter by date. I first tried to create an unbound column with unboundexpression with GetData(DateColumn) and it worked, all the strings with dates where converted to date and the other was #Err. And i was able to filter by date. but i wanted to control these #Err: its empty? its "otherstring"? or its something other?
so i tried with CustomUnboundColumnData function:
Try
value = Date.Parse(view.GetListSourceRowCellValue(listSourceRowIndex, columna))
Catch ex As Exception
If IsDBNull(view.GetListSourceRowCellValue(listSourceRowIndex, columna)) Then
value = DBNull.Value
Else
If view.GetListSourceRowCellValue(listSourceRowIndex, columna) = "" Then
value = DBNull.Value
ElseIf view.GetListSourceRowCellValue(listSourceRowIndex, columna) = "otherstring" Then
value = "#otherstring"
Else
value = "#Err"
End If
End If
End Try
but now when I try to filter by value (by date) i get an error:
Cannot compare two items in array.:
System.ArgumentException: The object must be of type String.
if I replace the "#Err" and "#otherstring" for DBNull.Value it works again. but i need to be able to put other strings.
Don't use the Try...End Try for fully expected errors. You are not using ex at all.
Use a TryParse. You can distinguish the nulls and other strings by assigning an obviously bogus date. Create a list of the bad strings for review.
Dim d As Date
Dim value As Date
Dim errList As New List(Of String)
If Date.TryParse(View.GetListSourceRowCellValue(listSourceRowIndex, columna), d) Then
value = d
ElseIf IsDBNull(View.GetListSourceRowCellValue(listSourceRowIndex, columna)) Then
value = CDate("January 2, 0001 ")
ElseIf View.GetListSourceRowCellValue(listSourceRowIndex, columna) = "" Then
value = CDate("January 2, 0001 ")
Else
value = CDate("January 1, 0001")
errList.Add(View.GetListSourceRowCellValue(listSourceRowIndex, columna).ToString)
End If

Managing Blank field from Linq Query

I am writing a method in VB.Net that runs a LINQ query on my database, and puts the information from the query into textboxes on a form.
Public Sub OpenPartWindow(ByVal partNumber As String)
Using dbContext As New DB_LINQDataContext
Dim query = (From p In dbContext.Parts
Join ppl In dbContext.Part_Price_Logs On p.Part_ID Equals ppl.Part_ID
Where p.Part_Number = partNumber
Select p.Part_Number, p.Part_Description, p.Part_Information, p.Supplier.Supplier_Name, _
p.Part_Manufacturer.Manufacturer_Name, p.Part_Subcategory.Part_Category.Category_Description, _
p.Part_Subcategory.Subcategory_Description, ppl.Cost_Per_Unit, ppl.Discount_Percentage).First()
MessageBox.Show(query.ToString())
txtPartNum.Text = query.Part_Number.ToString()
txtSupplier.Text = query.Supplier_Name.ToString()
txtManufacturer.Text = query.Manufacturer_Name.ToString()
txtPrice.Text = query.Cost_Per_Unit.ToString()
txtDiscount.Text = query.Discount_Percentage.ToString()
txtDescription.Text = query.Part_Description.ToString()
txtInfo.Text = query.Part_Information.ToString()
Me.Show()
End Using
End Sub
The issue I am having right now is with the last field, the txtInfo TextBox. You see the Part_Information field on the database allows NULL values. So when I go to fill the field when the field is null I am getting a NullReferenceException, which is understandable. However I cannot find a way to get around this exception.
I've tried:
If Not IsNothing(query._Part_Information.ToString()) Then
As well as
If query.Part_Information.Length > 0 Then
As If statements to run through first. But I keep getting the error each time. So I am confused on how I am suppose to deal with this error.
You can use either of the following options:
txtInfo.Text = String.Format("{0}", query.Part_Information)
txtInfo.Text = $"{query.Part_Information}" → $ - String Interpolation
txtInfo.Text = query.Part_Information?.ToString() → ?. - Null Conditional Operator
If query.Part_Information is null, the first two expressions result in Sting.Empty and the last one result in Nothing.

Parameter Form to Input Query Criteria with Multiple Fields Either With a Value or Null

I have built a form with unbound fields designed so a user can input a date range, a facility name (these come from a combobox), and a badge number to generate a query in Access. I want to be able to return results within the selected date range for all facilities if the field is left blank or just the ones for a particular facility if one is selected. I also want to be able to limit the results to those that match an person's badge number.
So the possibilities I want would be:
Date Range = defined by user | Facility - All if not selected | Badge # = All if not selected
Date Range = defined by user | Facility - All if not selected | Badge # = defined by user
Date Range = defined by user | Facility - defined by user | Badge # = All if not selected
Date Range = defined by user | Facility - defined by user | Badge # = defined by user
I originally built it with just the date range and facility name and it worked fine. When I try to add in the Badge # it doesn't really work correctly.
My SQL for the WHERE TO section is:
WHERE (((Diversion.Transaction_Date) Between [Forms]![Parameters]![FromDate] And [Forms]![Parameters]![ToDate])
AND ((Diversion.Employee_Badge_Number)=[Forms]![Parameters]![BadgeNumber])
AND ((Diversion.Facility)=[Forms]![Parameters]![FacilitySelect]))
OR (((Diversion.Transaction_Date) Between [Forms]![Parameters]![FromDate] And [Forms]![Parameters]![ToDate])
AND ((Diversion.Facility)=[Forms]![Parameters]![FacilitySelect])
AND ((([Diversion].[Employee_Badge_Number]) Like [Forms]![Parameters]![BadgeNumber]) Is Null))
OR (((Diversion.Transaction_Date) Between [Forms]![Parameters]![FromDate] And [Forms]![Parameters]![ToDate])
AND ((Diversion.Employee_Badge_Number)=[Forms]![Parameters]![BadgeNumber])
AND ((([Diversion].[Facility]) Like [Forms]![Parameters]![FacilitySelect]) Is Null))
OR (((Diversion.Transaction_Date) Between [Forms]![Parameters]![FromDate] And [Forms]![Parameters]![ToDate])
AND ((([Diversion].[Employee_Badge_Number]) Like [Forms]![Parameters]![BadgeNumber]) Is Null)
AND ((([Diversion].[Facility]) Like [Forms]![Parameters]![FacilitySelect]) Is Null))
OR (((([Diversion].[Facility]) Like [Forms]![Parameters]![FacilitySelect]) Is Null));
To me, it looks like it is including the four possible results that I want to get from the form, but it isn't working right. For instance, if I leave the facility field blank, and define the badge number, it is still giving me all of the results. If I define the facility and define the badge number it does give me the correct results.
Any ideas?
This might give you some ideas building a dynamic query with multiple criteria values. In this example the user can pick any number of the criteria. It is written in VB.Net. It works with Access. I check each field to see if any criteria was provided then append it to the query if there is a valid value.
I used Interpolated strings just because it is easier to see where the spaces go. The alternative is:
String.Format("RoasterId = {0} ", itgRoaster)
I also used a String builder which is an efficient way to alter strings without the overhead of creating and disposing of them with each append. You could just use &= if this is not available in VBA.
Dim bolNeedAnd As Boolean = False
Dim sb As New Text.StringBuilder
sb.Append("SELECT Coffees.ID, Coffees.[Name], Coffees.RoasterID, Roasters.[Name], Coffees.[Type],Coffees.Rating, Coffees.Comment, Coffees.Description, Coffees.Roast, Coffees.IsExtraBold, Coffees.IsFavorite
From Coffees Inner Join Roasters on Coffees.RoasterID = Roasters.ID Where ")
If itgRoaster <> 0 Then
sb.Append($"RoasterID = {itgRoaster} ")
bolNeedAnd = True
End If
If strRoast <> "" Then
If bolNeedAnd Then
sb.Append($"AND Roast = '{strRoast}' ")
Else
sb.Append($"Roast = '{strRoast}' ")
End If
bolNeedAnd = True
End If
If strType <> "" Then
If bolNeedAnd Then
sb.Append($"AND Type = '{strType}' ")
Else
sb.Append($"Type = '{strType}' ")
End If
bolNeedAnd = True
End If
If strRating <> "" Then
If bolNeedAnd Then
sb.Append($"AND Rating = '{strRating}' ")
Else
sb.Append($"Rating = '{strRating}' ")
End If
bolNeedAnd = True
End If
If bolBold Then
If bolNeedAnd Then
sb.Append("AND IsExtraBold = 1 ")
Else
sb.Append("IsExtraBold = 1 ")
End If
bolNeedAnd = True
End If
If bolFavorite Then
If bolNeedAnd Then
sb.Append("AND IsFavorite = 1 ")
Else
sb.Append("IsFavorite = 1 ")
End If
End If
sb.Append("Order By Coffees.[Name];")
Debug.Print(sb.ToString)
Dim cmd As New OleDbCommand With {
.Connection = cn,
.CommandType = CommandType.Text,
.CommandText = sb.ToString}

Highlight DataGridViewRows based on value comparison with other rows

I have a Part class with the fields list in the code below. I have a DataGridView control, which I am filtering with the Advanced DGV (ADGV) DLL from NUGET. I must include the ADGV in my winform. I currently have a DataGridView, a search box on the form, and a button to run the following function. I need to go through all of the visible rows, collect a unique list of part numbers with their most recent revisions, and then color the rows in DataGridView which are out of date by checking the part number and rev on each row against the mostuptodate list. For 45,000 entries displayed in DataGridView, this take ~17 secs. For ~50 entries, it take ~1.2 seconds. This is extremely inefficient, but I can't see a way to cut the time down.
Sub highlightOutdatedParts()
'Purpose: use the results in the datagridview control, find the most recent revision of each part, and
' highlight all outdated parts relative to their respective most recent revisions
'SORT BY PART NUMBER AND THEN BY REV
If resultsGrid.ColumnCount = 0 Or resultsGrid.RowCount = 0 Then Exit Sub
Dim stopwatch As New Stopwatch
stopwatch.Start()
resultsGrid.Sort(resultsGrid.Columns("PartNumber"), ListSortDirection.Ascending)
Dim iBag As New ConcurrentBag(Of Part)
Dim sortedList As Generic.List(Of Part)
For Each row As DataGridViewRow In resultsGrid.Rows
If row.Visible = True Then
Dim iPart As New Part()
Try
iPart.Row = row.Cells(0).Value
iPart.Workbook = CStr(row.Cells(1).Value)
iPart.Worksheet = CStr(row.Cells(2).Value)
iPart.Product = CStr(row.Cells(3).Value)
iPart.PartNumber = CStr(row.Cells(4).Value)
iPart.ItemNo = CStr(row.Cells(5).Value)
iPart.Rev = CStr(row.Cells(6).Value)
iPart.Description = CStr(row.Cells(7).Value)
iPart.Units = CStr(row.Cells(8).Value)
iPart.Type = CStr(row.Cells(9).Value)
iPart.PurchCtgy = CStr(row.Cells(10).Value)
iPart.Qty = CDbl(row.Cells(11).Value)
iPart.TtlPerProd = CDbl(row.Cells(12).Value)
iPart.Hierarchy = CStr(row.Cells(13).Value)
iBag.Add(iPart)
Catch ice As InvalidCastException
Catch nre As NullReferenceException
End Try
End If
Next
sortedList = (From c In iBag Order By c.PartNumber, c.Rev).ToList() ' sort and convert to list
Dim mostUTDRevList As New Generic.List(Of Part) ' list of most up to date parts, by Rev letter
For sl As Integer = sortedList.Count - 1 To 0 Step -1 'start at end of list and work to beginning
Dim query = From entry In mostUTDRevList ' check if part number already exists in most up to date list
Where entry.PartNumber = sortedList(sl).PartNumber
Select entry
If query.Count = 0 Then ' if this part does not already exist in the list, add.
mostUTDRevList.Add(sortedList(sl))
End If
Next
'HIGHLIGHT DATAGRIDVIEW ROWS WHERE PART NUMBERS ARE OUT OF DATE
For Each row As DataGridViewRow In resultsGrid.Rows
' if that part with that Rev does not exist in the list, it must be out of date
Try
Dim rowPN As String = CStr(row.Cells(4).Value).ToUpper ' get part number
Dim rowR As String = CStr(row.Cells(6).Value).ToUpper ' get Rev
Dim query = From entry In mostUTDRevList ' check if that part number with that Rev is in the list.
Where entry.PartNumber.ToUpper.Equals(rowPN) AndAlso
entry.Rev.ToUpper.Equals(rowR)
Select entry
If query.Count = 0 Then ' if the part is out of date highlight its' row
row.DefaultCellStyle.BackColor = Color.Chocolate
End If
Catch ex As NullReferenceException
Catch ice As InvalidCastException
End Try
Next
resultsGrid.Select()
stopwatch.Stop()
If Not BackgroundWorker1.IsBusy() Then timertextbox.Text = stopwatch.Elapsed.TotalSeconds.ToString & " secs"
MessageBox.Show("Highlighting completed successfully.")
End Sub
It is almost always faster to work with the data than the control. The control is simply the means to present a view of the data (in a grid) to the users. Working with the data from there requires too much converting to be effieicent. Then, use the DGV events to highlight the rows
Its hard to tell all the details of what you are doing, but it looks like you are comparing the data to itself (as opposed to some concrete table where the lastest revision codes are defined). Nor is it clear why the datasources are collections, ConcurrentBags etc. The key would be to use collections optimized for the job.
To demonstrate, I have a table with 75,000 rows; the product codes are randomly selected from a pool of 25,000 and a revision code is a random integer (1-9). After the DGV datasource is built (a DataTable) a LookUp is created from the ProductCode-Revision pair. This is done once and once only:
' form level declaration
Private PRCodes As ILookup(Of String, Int32)
' go thru table
' group by the product code
' create an anon Name-Value object for each,
' storing the code and highest rev number
' convert result to a LookUp
PRCodes = dtSample.AsEnumerable.
GroupBy(Function(g) g.Item("ProductCode"),
Function(key, values) New With {.Name = key.ToString(), .Value = values.
Max(Of Int32)(Function(j) j.Field(Of Int32)("RevCode"))
}).
ToLookup(Of String, Int32)(Function(k) k.Name, Function(v) v.Value)
Elapsed time via stopwatch: 81 milliseconds to create the collection of 23731 items. The code uses an anonymous type to store a Max Revision code for each product code. A concrete class could also be used. If you're worried about mixed casing, use .ToLowerInvariant() when creating the LookUp (not ToUpper -- see What's Wrong With Turkey?) and again later when looking up the max rev.
Then rather than looping thru the DGV rows use the RowPrePaint event:
If e.RowIndex = -1 Then Return
If dgv1.Rows(e.RowIndex).IsNewRow Then Return
' .ToLowerInvariant() if the casing can vary row to row
Dim pc = dgv1.Rows(e.RowIndex).Cells("ProductCode").Value.ToString()
Dim rv = Convert.ToInt32(dgv1.Rows(e.RowIndex).Cells("RevCode").Value)
Dim item = PRCodes(pc)(0)
If item > rv Then
dgv1.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.MistyRose
End If
Notes
It takes some time to create the DataSource, but 75,000 rows is a lot to throw at a user
The time to create the LookUp is minimal - barely measurable
There is no noticeable wait in displaying them because a) the LookUp is made for this sort of thing, b) rows are done as needed when they are displayed. Row # 19,999 may never be processed if the user never scrolls that far.
This is all geared to just color a row. If you needed to save the Current/NotCurrent state for each row, add a Boolean column to the DataTable and loop on that. The column can be invisible if to hide it from the user.
The random data results in 47,000 out of date RevCodes. Processing 75k rows in the DataTable to set the flag takes 591 milliseconds. You would want to do this before you set the DataTable as the DataSource to prevent changes to the data resulting in various events in the control.
In general, the time to harvest the max RevCode flag and even tag the out of date rows is a trivial increment to creating the datasource.
The Result:
The data view is sorted by ProductCode so that the coloring of lower RevCodes is apparent.
We surely cant grok all the details and constraints of the system from a small snippet - even the data types and original datasource are a guess for us. However, this should provide some help with better look-up methods, and the concept of working with the data rather than the user's view.
One thing is the revision code - yours is treating them as a string. If this is alphanumeric, it may well not compare correctly - "9" sorts/compares higher than "834" or "1JW".
See also:
Lookup(Of TKey, TElement) Class
Anonymous Types
The solution was spurred in part by #Plutonix.
Sub highlightOutdatedParts()
If resultsGrid.ColumnCount = 0 Or resultsGrid.RowCount = 0 Then Exit Sub
Dim stopwatch As New Stopwatch
stopwatch.Start()
resultsGrid.DataSource.DefaultView.Sort = "PartNumber ASC, Rev DESC"
resultsGrid.Update()
'HIGHLIGHT DATAGRIDVIEW ROWS WHERE PART NUMBERS ARE OUT OF DATE
Dim irow As Long = 0
Do While irow <= resultsGrid.RowCount - 2
' if that part with that Rev does not exist in the list, it must be out of date
Dim utdPN As String = resultsGrid.Rows(irow).Cells(4).Value.ToString().ToUpper()
Dim utdRev As String = resultsGrid.Rows(irow).Cells(6).Value.ToString().ToUpper()
Dim iirow As Long = irow + 1
'If iirow > resultsGrid.RowCount - 1 Then Exit Do
Dim activePN As String = Nothing
Dim activeRev As String = Nothing
Try
activePN = resultsGrid.Rows(iirow).Cells(4).Value.ToString().ToUpper()
activeRev = resultsGrid.Rows(iirow).Cells(6).Value.ToString().ToUpper()
Catch ex As NullReferenceException
End Try
Do While activePN = utdPN
If iirow > resultsGrid.RowCount - 1 Then Exit Do
If activeRev <> utdRev Then
resultsGrid.Rows(iirow).DefaultCellStyle.BackColor = Color.Chocolate
End If
iirow += 1
Try
activePN = resultsGrid.Rows(iirow).Cells(4).Value.ToString().ToUpper()
activeRev = resultsGrid.Rows(iirow).Cells(6).Value.ToString().ToUpper()
Catch nre As NullReferenceException
Catch aoore As ArgumentOutOfRangeException
End Try
Loop
irow = iirow
Loop
resultsGrid.Select()
stopwatch.Stop()
If Not BackgroundWorker1.IsBusy() Then
timertextbox.Text = stopwatch.Elapsed.TotalSeconds.ToString & " secs"
resultcounttextbox.Text = resultsGrid.RowCount - 1 & " results"
End If
MessageBox.Show("Highlighting completed successfully.")
End Sub

is it possible to use 2 parameters with dataview.rowfilter? How?

It works with 1 parameter (i_index) but if I also use i_datum, I get an error like: "Conversion from string "park_id = 100" to type 'Long' is not valid."
Public Function detail_kalender(ByVal i_index As Int16, ByVal i_datum As Date) As DataRowView
Dim dv As DataView
Dim anyrow As DataRowView
dv = New DataView
With dv
.Table = myds.Tables("kalender")
.AllowDelete = True
.AllowEdit = True
.AllowNew = True
.RowFilter = "park_id = " & i_index And "datum =" & i_datum
End With
anyrow = dv.Item(0) 'geeft de eerste rij van de dataview dv
' Simple bind to a TextBox control
dv = mydt_parken.DefaultView
Return anyrow
dv.Dispose()
dv = Nothing
End Function
Looking at the code you have there for the row filter, it will translate to:
.RowFilter = "park_id = 100datum = something"
Notice the lack of space between the park_id and next field. You also need to add "and" (I think?)
Try:
.RowFilter = String.Format("park_id = {0} and datum = {1}", i_index.Tostring, i_datum.ToString)
You may need to tinker this to include apostrophes depending on the types of your data (i.e. change to
... and datum = **'**{1}**'**
if it's a string)
EDIT: In response to your comment.
Have a look on this page for some useful tips. For dates you use the # symbols.
.RowFilter = String.Format("park_id = {0} and datum = #{1}#", i_index.Tostring, i_datum.ToString)
EDIT: In response to your second comment ( formatException"String was not recognized as a valid DateTime." ):
This is a little trickier. I'll list a possible quick solution, but by no means the best solution.
Dim customDateFormat As String = "MM/dd/yyyy hh:mm:ss"
.RowFilter = String.Format("park_id = {0} and datum >= #{1}# and datum <= #{2}#",
i_index.ToString,
New DateTime(i_datum.Year, i_datum.Month, i_datum.Day, 0, 0, 0).ToString(customDateFormat),
New DateTime(i_datum.Year, i_datum.Month, i_datum.Day, 23, 59, 59).ToString(customDateFormat))
Basically, the chances are that when you compare your date's to the date times in the database, you want to ignore the time? (Making an assumption here). One way of doing this is to compare your database data with your date, ensuring it is between 00:00 AM and 23:59:59 PM.
I've included a customDateFormat string which you can tamper if you need to, in order to reflect your locale. I know that date constants ignore locale, but I've no idea what it does inside the RowFilter as a string, and whether the database locale has any effect on it. If the above doesn't work, you can change the date format string to match your locale to see if that helps.
Try using instead:
.RowFilter = "park_id = " & i_index & " And datum =" & i_datum