strange behaviour of linq usage - linq

I have two tables (t1 and t2) and I select two fields from this tables (f1 and f2).
The list Queries contains the selected data. In this case there are 2 entry with 2 rows.
This is the code:
Dim FieldIndexes As New List(Of Integer)
Dim Queries As New List(Of IEnumerable(Of Object()))
For i = 0 To _SqlSyntaxChecker.QueriedTables.Count - 1
FieldIndexes.Clear()
For j = 0 To _SqlSyntaxChecker.DataFields.Count - 1
If _SqlSyntaxChecker.QueriedTables(i).TableName = _SqlSyntaxChecker.DataFields(j).TableName Then FieldIndexes.Add(_SqlSyntaxChecker.DataFields(j).FieldIndexInDataTable)
Next
Dim query = _SqlSyntaxChecker.QueriedTables(i).Rows.Select(Function(Row) FieldIndexes.Select(Function(FieldIndex) Row.Item(FieldIndex)).ToArray)
Queries.Add(query)
For Each item In Queries(i)
_OutputDataTable.Rows.Add(item)
Next
Next
And this is the result:
As you see, everything is ok, I expected this result (it is now not important, that I have on the image 4 rows in one column).
Originally, I wanted to populate the _OutputDataTable outside the for cykle, like so:
Dim FieldIndexes As New List(Of Integer)
Dim Queries As New List(Of IEnumerable(Of Object()))
For i = 0 To _SqlSyntaxChecker.QueriedTables.Count - 1
FieldIndexes.Clear()
For j = 0 To _SqlSyntaxChecker.DataFields.Count - 1
If _SqlSyntaxChecker.QueriedTables(i).TableName = _SqlSyntaxChecker.DataFields(j).TableName Then FieldIndexes.Add(_SqlSyntaxChecker.DataFields(j).FieldIndexInDataTable)
Next
Dim query = _SqlSyntaxChecker.QueriedTables(i).Rows.Select(Function(Row) FieldIndexes.Select(Function(FieldIndex) Row.Item(FieldIndex)).ToArray)
Queries.Add(query)
Next
For Each q In Queries
For Each item In q
_OutputDataTable.Rows.Add(item)
Next
Next
But as you see, the result is wrong:
The result should be the same.
What can cause this?

Your query is linked to i field (using closures). But queries are execute after first cycle, so i already have its last value when query is executed.
You can check "Access to modified closure" topic over internet (mainly it's about delegates but actually same thing works for expression trees).

Related

How to check if array value exists in Cell?

I am new to vbscript and I am creating a script to reduce my day by day duplicate efforts. I have a column in an excel sheet which holds values. There are certain cells under this column where multiple values exists. Now I have an array which has some values which needs to be looked up / searched within each row under this column and delete the row if array values are not present within the row.
I tried to search the array values in the rows using InStr function and it worked if cell contains only one value. Code is attached below.
This sub is not working as expected if cell contains multiple values. e.g. Project 1 [ALT + ENTER] Dummy Project
Hence I tried to use Find and Search methods. I am unable to get the expected results using these methods too.
Fix Version/s
Row 1 - Project 3 a
Row 2 - Project 2 'spaces at the end
Row 3 - Project 4
---------
Project 1
Row 4 - Project 5
Row 5 - Project 1
Find method - No rows deleted
Replace method - Getting syntax error where I used Search method in place of InStr function as below,
If objWorksheet1.Cells(iDelCnt, fixVerColNum).Search(objWorksheet1.Cells(iDelCnt, fixVerColNum).Value, projectFilter(jDelCnt)) <> 0 Then
Please assist. Thanks in advance.
Expected - I expect row 'x' to be deleted if array value doesn't exists in Cells(x,y)
Dim objExcel, objWorkbook, objWorksheet1
Dim iDelCnt, jDelCnt, projectFilter, lRow, fixVerColNum, tempCell, deleteCounter, InStrTest
Set objExcel = CreateObject("Excel.Application")
objExcel.Application.Visible = True
Set objWorkbook = objExcel.Workbooks.Open(filePath) ' Location of the file on drive
Set objWorksheet1 = objWorkbook.Worksheets(1)
projectFilter = Array("Project 1","Project 2", "Project 3")
fixVerColNum = objExcel.Match("Fix Version/s", objWorksheet1.Range("A1:T1"), 0) 'Identify "Fix Version(s)" column number
lRow = objWorksheet1.Range("A1").CurrentRegion.Rows.Count
deleteCounter = 0
For iDelCnt = lRow to 2 Step -1
For jDelCnt = LBound(projectFilter) to UBound(projectFilter)
If InStr(1, objWorksheet1.Cells(iDelCnt, fixVerColNum).Value, projectFilter(jDelCnt), 1) <> 0 Then
deleteCounter = 1
Exit For
Else
deleteCounter = 0
End If
Next
If deleteCounter = 0 Then
objWorksheet1.Rows(iDelCnt).EntireRow.Delete
End If
Next

Arrays to filter, copy and past - quick macro method

I want to make an Excel workbook that I have much quicker.
I have a big product database with the product names, quantities, delivery number and delivery date (ProductDB). I put in another sheet the products that I have sold (product names and quantity sold) and want to filter and copy those that are corresponding from the database so I can calculate in the second step the remaining quantity and past the remaining quantity to the database.
Everything is working well and the calculation is good. The only thing is, the Advancedfilter xlfiltercopy option is too slow if I have to input 5000 lines of product names.
I have heard that arrays are much faster. How could I do that? The current way I do it is like this:
Sub UseFilter()
Application.ScreenUpdating = False
Sheet1.Range("G1:Z100000").Cells.Delete
Dim lastrow As Long, c As Range
Dim myrange As Range
Dim rngCell As Range
Dim wksSheet As Worksheet
Dim wksSheetDB As Worksheet
lastrow = Sheet1.Cells(Rows.Count, "A").End(xlUp).Row
Sheet1.Columns("G").NumberFormat = "0"
Filter product codes from the database according to sold product codes:
Set myrange = Range("A1:A" & lastrow)
For Each c In myrange
If Len(c.Value) <> 0 Then
ThisWorkbook.Worksheets(Worksheets.Count).Columns("A:D").AdvancedFilter xlFilterCopy, _
Sheet1.Range("A1:A" & lastrow), Sheet1.Range("G1"), False
End If
Next
Sort the filtered list first by product code, then by the delivery number:
Dim lngRowMax As Long
Dim wsf As WorksheetFunction
With Sheet1
lastrow = Cells(Rows.Count, 8).End(xlUp).Row
Range("G1:J" & lastrow).Sort Key1:=Range("G1:G" & lastrow), _
Order1:=xlAscending, Key2:=Range("I1:I" & lastrow), _
Order2:=xlAscending, Header:=xlYes, DataOption1:=xlSortTextAsNumbers
Set wsf = Application.WorksheetFunction
lngRowMax = .UsedRange.Rows.Count
End With
I'm only interested in filtering and copying of the corresponding product information (name (A), quantity (B), delivery nr (C) and date (D)). Does anyone know how I can do that?
Thank you very much in advance. I'm really looking forward for a solution that improves the pace of the file. Currently it is unbelievably slow.
i had the same problem with advanced filter being so slow. you might want to consider using dictionary. for my 2 spreadsheets i wanted to compare i made 2 dictionaries and compared the values and it was so amazingly fast. dictionaries are really easy and a simple google search you can find a ton of tutorials and examples. good luck.
There is a possible solution with dictionaries, but I have only one small issue. I will explain after the code:
'Count num rows in the database
NumRowsDB = ThisWorkbook.Worksheets(Worksheets.Count).Range("A2", Range("A2").End(xlDown)).Rows.Count
' --------------------- SAVE DATABASE DATA -----------------------
'Dictionary for all DB data
Set dbDict = CreateObject("Scripting.Dictionary")
Set dbRange = Range("A2:A" & (NumRowsDB + 1))
For Each SKU In dbRange
If Len(SKU.Value) <> 0 Then
' check if the SKU allready exists, if not create a new array list for that dictionary entry
' a list is necessary because there can be multiple entries in the db range with the same SKU
If Not dbDict.Exists(CStr(SKU.Value)) Then
Set prodList = CreateObject("System.Collections.ArrayList")
dbDict.Add CStr(SKU.Value), prodList
End If
' for this specific product code, save the range where the product information is saved in the dictionary
rangeStr = "A" & SKU.Row & ":D" & SKU.Row
dbDict(CStr(SKU.Value)).Add (rangeStr)
End If
Next
' ----------- READ SALE/Reverse/Consumption INFO ------------------
NumRowsSale = Range("A2", Range("A2").End(xlDown)).Rows.Count
Set saleRange = Range("A2:A" & (NumRowsSale + 1))
Dim unionRange As Range
For Each sale In saleRange
' check if the SKU for the sale exists in db
If Len(sale.Value) <> 0 And dbDict.Exists(CStr(sale.Value)) Then
For Each dbRange In dbDict(CStr(sale.Value))
If unionRange Is Nothing Then
Set unionRange = Range(dbRange)
Else
Set unionRange = Union(unionRange, Range(dbRange))
End If
Next
End If
Next
unionRange.Copy Destination:=Range("G2") 'copy all received ranges to G2
Set dbDict = Nothing
The line "NumRowsDB = ThisWorkbook.Worksheets(Worksheets.Count).Range("A2", Range("A2").End(xlDown)).Rows.Count" is not working. I have to refer to another sheet (the last sheet which is the current database) to get the data. What is the problem that I cannot refer to another sheet in the same workbook?
Thank you for your suggestions.

Excel VBA - Stops Selecting Visible Rows on Filter after many interations

My code takes a file name from TextBox1, opens that file and indexes through all the unique values in column B. It then takes a second file, file name in TextBox2, and filters it based on the current index from the first file. It then takes the filtered results and copies them to a sheet in the new workbook. A new sheet is then generated in the new workbook to paste the next filtered result.
My issue is that I have many rows of data and for some reason the filtered data is not be selected after many iterations. My program selects all filtered data when it starts, but at some point it just begins selecting the headers instead of all the visible cells. Let me know if I am missing something or if there is a quick workaround. Thank you.
Sub NewFileGenerate()
Dim I As Integer
Dim N As Integer
Dim X As Integer
Dim IndexedCell As String
X = 1
Windows(TextBox1.Value).Activate
'Need to count only populated cells
I = Sheets(1).Columns(2).Cells.SpecialCells(xlCellTypeConstants).Count
Set Newbook = Workbooks.Add
For N = 2 To I - 1
Application.CutCopyMode = True
Windows(TextBox1.Value).Activate
IndexedCell = Cells(N, 2).Value
Windows(TextBox2.Value).Activate
With Sheets(1)
With .Range("A1", "Z" & I - 1)
.AutoFilter Field:=5, Criteria1:=IndexedContract
.SpecialCells(xlCellTypeVisible).Copy
End With
End With
Newbook.Activate
ActiveSheet.Paste Destination:=Sheets(X).Range("A1:Z1")
Cells.Select
Selection.Font.Size = 10
Cells.EntireColumn.AutoFit
Cells.Select
X = X + 1
If X > 3 Then
Worksheets.Add(After:=Worksheets(Worksheets.Count)).Name = "Sheet" & X
End If
Application.CutCopyMode = False
Next N
End Sub
I'm guessing that your source worksheet (textbox2.value) has more rows than your index worksheet (textbox1.value). You set I equal to the number of rows in your index worksheet, then you tell the autofilter to only use that number of rows. You need to change the line "With .Range("A1", "Z" & I - 1)" so it picks up all of the rows in your source worksheet.

How to reference the same table twice?

First of all I have to admit I'm very much a novice in Linq and Lambda expressions.
I'm trying to get the following SQL statement into a Linq statement (using lamda expressions):
select *
from dbo.tblStockTransfers t1,
dbo.tblSuppliers t2
where t1.SupplierID = t2.SupplierID
and t2.WarehouseID in (1,2,3)
and t1.GoodsPickedUp = 1
and Not exists
(select 1 from dbo.tblStockTransfers t3
where t3.TransferOutID = t1.TransferID and t3.TransferConfirm = 1)
My class StockTransfer is an aggregate root and has it's own repository.
Now so far I got the following (the variable allowedWarehouses contains the list of warehouse IDs):
Return GetObjectSet().Where(Function(st) allowedWarehouses.Contains(st.Supplier.WarehouseID) And st.GoodsPickedUp = True)
This works fine, but obviously is missing the " and not exists ..." part (the last 3 lines of the SQL code at the top of this posting).
I know that Linq doesn't have a "not exists", but you can use the "Any" method for this.
Here's a working example of this elsewhere in my code:
Return GetObjectSet().Where(Function(sw) sw.Active = True And Not sw.Suppliers.Any(Function(sp) sp.WarehouseID = sw.Id))
This works fine and will give me any warehouses which are not linked to a supplier yet.
As you can see in the above example this is fine as I'm referring to the related table "Suppliers".
However, in the SQL code I'm now trying to convert into Linq, the "not exists" is not on a linked table but on itself. Is there a way I can create a 2nd reference on the main table and use that in a ".. not ..any" part. Maybe something like:
Return GetObjectSet().Where(Function(st) allowedWarehouses.Contains(st.Supplier.WarehouseID) And st.GoodsPickedUp = True And Not st2.Any(st2.TransferOutID = st.TransferId and st2.TransferConfirm = true)
But I don't know how to define st2 (i.e. in this case st2 would be a 2nd alias to StockTransfer).
Any Help would be greatly appreciated.
This is not the answer to the question, but it is a work-around which does get me the result I need:
Dim st1 As List(Of StockTransfer) = GetObjectSet.Where(Function(st) allowedWarehouses.Contains(st.Supplier.WarehouseID) And st.GoodsPickedUp = True).ToList
Dim st2 As List(Of StockTransfer) = GetObjectSet.Where(Function(st) st.TransferConfirm = True).ToList
For Each st As StockTransfer In st2
st1.RemoveAll(Function(x) x.Id = st.TransferOutID)
Next
Return st1
I'm obviously cheating by splitting out the query in 2 parts, where each part ends up in a list and then I remove from list 1 any items which I've got in list 2 (removing the ones which would normally be ignored by the "not exists" part).
However, I would love to hear it if anyone can come up with a working solution using Linq and lambda expressions (as this does feel a bit like a cheat).
I would do it something like this:
Dim lsWareHouseIds As New List(Of Integer)() From {1,2,3}
dim obj= ( _
From t1 in db.tblStockTransfers _
join t2 in db.tblSuppliers _
on t1.SupplierID equals company.SupplierID _
where lsWareHouseIds.Contains(t2.WarehouseID) _
andalso t1.GoodsPickedUp =1 _
andalso Not _
(
from t3 in db.tblStockTransfers _
where t3.TransferConfirm=1 _
select t3.TransferOutID _
).Contains(t1.TransferID) _
select t1 _
)
I did see you comment and you answer. Can't you do like this:
GetObjectSet.Where(Function(st) _
allowedWarehouses.Contains(st.Supplier.WarehouseID) And st.GoodsPickedUp = True _
Andalso Not _
GetObjectSet.Where(Function(st) _
st.TransferConfirm = True).Any(Function(x) x.Id = st.TransferOutID)).ToList

How to combine 2 LINQ into one

I am trying to populate a treeview control with some info and for that I am using 2 separate LINQ statements as follows;
Dim a = From i In CustomerTable _
Group By ShipToCustName = i.Item(6), BillToCustName = i.Item(4) Into Details = Group, Count() _
Order By ShipToCustName
Dim b = From x In a _
Group By ShipToName = x.ShipToCustName Into Group, Count() _
Order By ShipToName
For Each item In b
Console.WriteLine("Shitp To Location: " + item.ShipToName)
For Each x In item.Group
Console.WriteLine("...BillTo:" + x.BillToCustName)
For Each ticket In x.Details
Console.WriteLine("......TicketNum" + ticket.Item(0).ToString)
Next
Next
Next
Is there a way to combine A and B into one query ? Any help please ...
Well, really B is a single query. Remember that what comes back from a Linq statement is an IQueryable, not the actual result. This means that B combines its own expressions with that of A, but retrieval is not performed until you enumerate B. So B really is just a single query.

Resources