Linq Compiled Queries and int[] as parameter - linq

I'm using the following LINQ to SQL compiled query.
private static Func<MyDataContext, int[], int> MainSearchQuery =
CompiledQuery.Compile((MyDataContext db, int[] online ) =>
(from u in db.Users where online.Contains(u.username)
select u));
I know it is not possible to use sequence input paramter for a compiled query and im getting “Parameters cannot be sequences” error when running it.
On another post here related , I saw that there is some solution but I couldn't understand it.
Does anyone know to use complied query with array as input paramter?
Please post example if you do.

Like the post that you referenced, it's not really possible out of the box. The post also references creating your own query provider, but it's a bit of overhead and complexity that you probably don't need.
You have a few options here:
Don't use a compiled query. Rather, have a method which will create a where clause from each item in the array resulting in something like this (psuedo-code):
where
online[0] == u.username ||
online[1] == u.username ||
...
online[n] == u.username
Note that you would have to use expression here to create each OR clause.
If you are using SQL Server 2008, create a scalar valued function which will take a table-valued parameter and a value to compare againt. It will return a bit (to indicate if the item is in the values in the table). Then expose that function through LINQ-to-SQL on your data context. From there, you should be able to create a CompiledQuery for that. Note that in this case, you should take an IEnumerable<string> (assuming username is of type string) instead of an array, just because you might have more than one way of representing a sequence of strings, and to SQL server for this operation, it won't matter what the order is.

One solution that I have found myself doing (for MS SQL 2005/2008). And I'm not sure if it is appropriate in all scenarios is to just write dynamic sql and execute it against the datacontext using the ExecuteQuery method.
For example, if I have an unbounded list that I am trying to pass to a query to do a contains...
' Mock a list of values
Dim ids as New List(of Integer)
ids.Add(1)
ids.Add(2)
' ....
ids.Add(1234)
Dim indivs = (From c In context.Individuals _
Where ids.Contains(c.Id) _
Select c).ToList
I would modify this query to create a SQL string to execute against the database directly like so...
Dim str As New Text.StringBuilder("")
Dim declareStmt as string = "declare #ids table (indivId int) " & vbcrlf)
For i As Integer = 0 To ids.Count - 1
str.Append("select " & ids(i).ToString() & " & vbcrlf)
If i < ids.Count Then
str.Append("union " & vbcrlf)
End If
Next
Dim selStatement As String = "select * From " & context.Mapping.GetTable(GetType(Individuals)).TableName & _
" indiv " & vbcrlf & _
" inner join #ids ids on indiv.id = ids.id"
Dim query = declareStmt & str.ToString & selStatement
Dim result = context.ExecuteQuery(of Individual)(query).ToList
So barring any syntax errors or bugs that I coded (the above is more or less psuedo code and not tested), the above will generate a table variable in SQL and execute an inner join against the desired table (Individuals in this example) and avoid the use of a "IN" statement.
Hope that helps someone out!

Related

VBA - Query with multiple AND statement not working

I'm having a hard time trying to gather data from an Oracle DB. I've managed to get the connection going, so the problem lies within the query. At the moment the query dont return anything, and neither VBA complains about it.
Here's the code:
Sub Connect_XXXX()
Dim conn As ADODB.Connection
Set conn = New ADODB.Connection
Dim rs As New ADODB.Recordset
Set rs = New ADODB.Recordset
Dim myQuery As ADODB.Command
Set myQuery = New ADODB.Command
conn.Open "Provider=OraOLEDB.Oracle;" & _
"Data Source=XXXX;" & _
"User Id=YYYY;" & _
"Password=ZZZZ"
myQuery.ActiveConnection = conn
myQuery.CommandText = "SELECT sta.index_id, sta.index_action as Action, sta.ticker, sta.company, sta.announcement_date as A_Date, sta.announcement_time as A_Time, " & _
"sta.effective_date as E_Date, dyn.index_supply_demand as BS_Shares, dyn.net_index_supply_demand as Net_BS_Shares, dyn.est_funding_trade as BS_Value, " & _
"dyn.trade_adv_perc/100 as Days_to_Trade, dyn.pre_index_weight/100 as Wgt_Old, dyn.post_index_weight/100 as Wgt_New, dyn.net_index_weight/100 as Wgt_Chg, " & _
"dyn.pre_est_index_holdings as Index_Hldgs_Old, dyn.post_est_index_holdings as Index_Hldgs_New, dyn.net_est_index_holdings as Index_Hldgs_Chg, sta.index_action_details as Details " & _
"FROM index_analysis.eq_index_actions_dyn dyn, index_analysis.eq_index_actions_static sta " & _
"WHERE (sta.action_id = dyn.action_id) AND (sta.announcement_date=dyn.price_date) AND (sta.announcement_date >= '01-January-2015') AND (sta.announcement_date <= '30-January-2015') " & _
"ORDER by sta.index_id,sta.announcement_date"
Set rs = myQuery.Execute
Sheets("Sheet1").Range("A1").CopyFromRecordset rs
rs.Close
conn.Close
End Sub
I've played around with the query a lot, and I've been able to get some results by removing some of the AND statements after the WHERE and trying with fewer fields on the SELECT statement, but I need them all in order for this results to work for me. The weird thing is that if I run the same query in a problem like Oracle Sql Developer (after connecting to the DB) it shows the results that I want. I could really use some help, Thanks!
If you are going to hard-code the date range, assuming announcement_date is a date, you'd want to compare against dates, not strings. You can use to_date with an explicit format mask to convert a string to a date,
sta.announcement_date >= to_date( '01-January-2015', 'DD-Month-YYYY')
or you can use a date literal which always has the format YYYY-MM-DD
sta.announcement_date >= date '2015-01-01'
My guess is that your code seems to work in SQL Developer because you happen to have configured your NLS_DATE_FORMAT to be 'DD-Month-YYYY' in SQL Developer.
In reality, you should really be using bind variables rather than hard-coding things like the date range. Assuming you bind a date value, the conversion (if any) from a string to a date would happen in VB and wouldn't depend on your session's NLS settings. There are also performance and security reasons to prefer bind variables.

LINQ to SQL, Visual Basic - Listbox Receives the Query NOT the Result

Overview
This is a homework assignment using LINQ to SQL in a Visual Basic application. It is correct in most ways except that I have a partially broken result. Rather than adding the result of my second query to my listbox, my code adds a weird representation of the query itself. Below is a description of the DB, followed by my output (intended and actual), and finally my code. Please point me toward the broken concept so I can figure out what I am missing. Thanks much.
DB info
I am using two tables, called Members and Payments, from one DB. Members has a primary key called ID and also has the fields first_name and last_name. Payments has a foreign key called Members_Id, which is associated to the Member's primary key; Payments also has the payment values under the column Payments.
Output should be like this
Member name = John Smith
$48.00, 10/20/2005
$44.00, 3/11/2006
But is this instead
Member name = SELECT ([t0].[First_Name] + #p1) + [t0].[Last_Name]
AS [value]FROM[dbo].[Members] AS [t0].[ID] = #p0
$48.00, 10/20/2005
$44.00, 3/11/2006
My VB Code
Public Class FormPaymentsGroup
Private db As New KarateClassesDataContext
Private Sub FormPaymentsGroup_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Me.PaymentsTableAdapter.Fill(Me.KarateDataSet.Payments)
'Group payments by Member_ID (FKey) in the Payments table. (Working fine)
Dim IdQuery = From aMember In db.Payments
Group aMember By aMember.Member_Id
Into MemberPayments = Group
For Each memberID In IdQuery
' Use the passed member_Id to target the row in the Members table and return the first_name & last_name.
' PROBLEM: This only seems to be returning the query itself; not the result.
Dim currMemberID = memberID.Member_Id
Dim nameQuery = From aName In db.Members
Where aName.ID = currMemberID
Select aName.First_Name + " " + aName.Last_Name
Dim currName = nameQuery.ToString ' Load the query result into a portable variable.
LbxMemberPayments.Items.Add("Member name = " & currName) ' PROBLEM: This is where the name SHOULD BE posted to the listbox.
' This is bound to the Members table but directs it based on the above IdQuery.
For Each enteredPayment In memberID.MemberPayments
LbxMemberPayments.Items.Add(vbTab & FormatCurrency(enteredPayment.Amount) & ", " & enteredPayment.Payment_Date)
Next
LbxMemberPayments.Items.Add(vbCr) ' Carriage return formatting
Next
End Sub
End Class
change
Dim currName = nameQuery.ToString
to
Dim currName = nameQuery.FirstOrDefault

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

Accessing anonymous types returned via a dynamic link query

I have been trying to access the data returned from a dynamic linq query as an anonymous type. The advice I have found suggests that I should create a custom type and use it in the Select clause with the new keyword. I was directed to the followed Question for a code example:
System.LINQ.Dynamic: Select(" new (...)") into a List<T> (or any other enumerable collection of <T>)
This was indeed an excellent example which I incorporated into my code (which is VB so I had to do some translation).
My code compiles fine but When I run it , I get the error following error:
"Value cannot be null. Parameter name: member" at the following line from the example:
bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]);
This seems to be linked to expressions(i), which correctly contains two items as I am returning two fields from the database table. properties(i) holds the name of those two fields correctly. Any ideas as to what the value for member is supposed to be and where it should be found? Is it something from the database?
The where clause of this query works and when I run it as an anonymous type it brings back records (or rather two fields from records). The returned fields contain data not nulls.
Here is my VB version of the code from the example provided in the earlier Question. I have bolded or ** the line where the error occurs. Any ideas as to what is causing this?
Much appreciated.
Function ParseNew() As Expression
NextToken()
ValidateToken(TokenId.OpenParen, Res.OpenParenExpected)
NextToken()
Dim properties As New List(Of DynamicProperty)()
Dim expressions As New List(Of Expression)()
Do
Dim exprPos = tokenVal.pos
Dim expr = ParseExpression()
Dim propName As String
If TokenIdentifierIs("as") Then
NextToken()
propName = GetIdentifier()
NextToken()
Else
Dim [me] As MemberExpression = TryCast(expr, MemberExpression)
If [me] Is Nothing Then Throw ParseError(exprPos, Res.MissingAsClause)
propName = [me].Member.Name
End If
expressions.Add(expr)
properties.Add(New DynamicProperty(propName, expr.Type))
If tokenVal.id <> TokenId.Comma Then Exit Do
NextToken()
Loop
ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected)
NextToken()
'CODE added to support strongly-typed returns
Dim type As Type = If(newResultType, DynamicExpression.CreateClass(properties))
Dim bindings(properties.Count - 1) As MemberBinding
For i As Integer = 0 To bindings.Length - 1
**bindings(i) = Expression.Bind(type.GetProperty(properties(i).Name), expressions(i))**
Next
Return Expression.MemberInit(Expression.[New](type), bindings)
End Function

Jet Database and pass-through queries, parameters

I'm connecting to a Jet 4 DB through ODBC.
The Jet DB uses pass-through queries to an Oracle DB. This works so far (can't access the p-t queries directly, but creating a view on the query does the trick).
I need some subset of the data returned by the p-ts. Parameters would be best, but are not supported.
Two questions:
1) Jet does seem to be able to push some where-clauses to Oracle. Eg I have a passthrough query that returns 100k rows. A view on the p-t with a single flitering clause (eg "district = '1010'") is very fast, so the processing seems to happen on Oracle. Adding more clauses can slow the query down to a crawl, looping for minutes with high CPU utilization. Is there any documentation on what is passed on and what is done on in the Jet side?
2) There are lots of tutorials on how to create dynamic passthrough queries with VBA/Access. Is it possible to do this (or anything to that effect) with Jet accessed through ODBC?
Thanks
Martin
Edit:
Sorry for being so unclear.
I have a reporting tool that accesses a Jet db through ODBC. The Jet db contains some data and several passthrough queries to an Oracle db. A typical use case would be a generating report for a given department and a given date, using data from Jet and Oracle. This works very well in principle.
The problem is that passthrough queries cannot contain any parameters. A passthrough query works like a view, so I can simply execute "select * from pt_query where dep = 'a' and date = somedate". Jet, however, loads all rows from the pt and does a full scan on the client side. This is unusably slow for a 100k-rows view and I need to find a way to avoid that.
For some simple selects, Jet does seem to let Oracle do the hard work and does not load all rows, hence my question 1.
If that doesn't work, I need to find a way to force Jet to load only the data I need from Oracle for a given request.
I know that I can modify pts through Access VBA, but I only connect through ODBC, so I can only pass SQL to Jet, not call the vb api (unless its possible to inline VB in the SQL statement).
It is not impossible that the query is constructed to cause a table scan, and this is causing the problem.
You seem to be working in VBA. It is possible to construct quite a few interesting queries as SQL strings in VBA and save them to new queries, update existing queries, use them for record sources for forms, or open recordsets. You can use DAO or ADO, depending on what you want to do. I have Oracle, so all I can do is suggest ideas using SQL Server, the connection in square brackets can be got by looking at the connection of a linked table (CurrentDb.TableDefs("NameOfTable").Connect):
Dim cn As New ADODB.Connection
''You can use Microsoft.ACE.OLEDB.12.0 or Microsoft.Jet.OLEDB.4.0
scn = "Provider=Microsoft.ACE.OLEDB.12.0;User ID=Admin;Data Source=" _
& CurrentProject.FullName
cn.Open scn
''An insert query, but you can see that is would be easy enough to
''select from two different databases
s = "INSERT into [ODBC;Description=TEST;DRIVER=SQL Server;" _
& "SERVER=ServerName\SQLEXPRESS;Trusted_Connection=Yes;" _
& "DATABASE=test].Table2 (id, atext) select id, atext from table1"
cn.Execute s
Or
''http://www.carlprothman.net/Default.aspx?tabid=87
strConnect = _
"Provider=sqloledb;" & _
"Data Source=myServerName;" & _
"Initial Catalog=Test;" & _
"Integrated Security=SSPI"
With cmd
.ActiveConnection = strConnect
.CommandType = adCmdText
.CommandText = "SELECT ID, aText FROM table2 " _
& "WHERE ID=?"
.Parameters.Append .CreateParameter _
("ID", adInteger, adParamInput, , 1)
.CommandTimeout = 0
Set rs = .Execute
End With
Can you duplicate the PT query in your own db instead of linking to it in another db?
All the sql in the PT query should get executed on the linked server without Jet attempting to parse or execute it. It's in a foreign language from Jet's point of view.
I'll use code like this in the PT:
SELECT * FROM DHSVIEWS.ClaimHeaderV WHERE
DHSViews.claimheaderV.ClaimType = 'p' AND
DHSViews.claimheaderV.FinalVersionInd = 'y' AND
DHSViews.claimheaderV.ReimbursementAmount > 0 AND
DHSViews.claimheaderV.majorProgram = 'HH' AND
DHSViews.claimheaderV.ServiceDateFrom >= [qStart] AND
DHSViews.claimheaderV.ServiceDateFrom <= [qEnd];
and this in VBA:
Set qdef = db.QueryDefs(qryPT)
sqlOld = qdef.sql
iPosStart = InStr(sqlOld, "[")
sqlNew = sqlOld
Do While iPosStart > 0
iPosEnd = InStr(iPosStart, sqlNew, "]")
param = Mid(sqlNew, iPosStart + 1, iPosEnd - iPosStart - 1)
Select Case param
Case "qStart"
paramVal = "'" & rsQuarter("quarterStart") & "'"
Case "qEnd"
paramVal = "'" & rsQuarter("quarterEnd") & "'"
End Select
sqlNew = Mid(sqlNew, 1, iPosStart - 1) & paramVal & Mid(sqlNew, iPosEnd + 1)
iPosStart = InStr(iPosEnd, sqlNew, "[")
Loop
If sqlNew <> sqlOld Then
qdef.sql = sqlNew
End If
db.QueryDefs(rsPTAppend("append")).Execute
If sqlNew <> sqlOld Then
qdef.sql = sqlOld
End If

Resources