I'm trying to create a helper class to easily build dynamic LINQ queries. Code is heavily modified version of this excellent CodeProject article: http://www.codeproject.com/Articles/493917/Dynamic-Querying-with-LINQ-to-Entities-and-Express which uses the PredicateBuilder here: http://www.albahari.com/nutshell/predicatebuilder.aspx
I'm still in the process of cleaning up the code so variables names are rubbish at the moment and there is not one comment in sight.
Usage
Dim conditions As New List(Of Condition)
With conditions
.Add(New Condition With {.Field = "EatsPeople", .Operator = "Equals", .Value = False})
.Add(New Condition With {.Field = "Name", .Operator = "Equals", .Value = "Adric"})
.Add(New Condition With {.Field = "Description", .Operator = "Contains", .Value = "ugly"})
End With
Dim predicate = BuildPredicate(conditions)
Dim tester = data.Monsters.AsExpandable.Where(predicate).ToList
Entity
Public Class Monster
Public Property EatsPeople As Boolean
Public Property Name As String
Public Property Description As String
End Class
Storage for search:
Public Class Condition
Public Property Field As String
Public Property [Operator] As String
Public Property Value As Object
End Class
Methods
Public Function BuildPredicate(conditions As List(Of Condition)) As Expression(Of Func(Of Monster, Boolean))
Dim predicate = PredicateBuilder.True(Of Monster)()
For Each c In conditions
Dim dbFieldName = c.Field
Dim dbType = GetType(Monster)
Dim dbFieldMemberInfo = dbType.GetMember(dbFieldName, BindingFlags.IgnoreCase Or BindingFlags.Public Or BindingFlags.Instance).Single()
predicate = BuildExpression(c, dbType, dbFieldMemberInfo, predicate)
Next
Return predicate
End Function
Private Function CreateLambda(value As Object, method As MethodInfo, ByVal dbType As Type, ByVal dbFieldMemberInfo As MemberInfo) As Expression(Of Func(Of Monster, Boolean))
Dim dbTypeParameter = Expression.Parameter(dbType, "x")
Dim dbFieldMember = Expression.MakeMemberAccess(dbTypeParameter, dbFieldMemberInfo)
Dim criterionConstant = New Expression() {Expression.Constant(value)}
Dim containsCall = Expression.Call(dbFieldMember, method, criterionConstant)
Return TryCast(Expression.Lambda(containsCall, dbTypeParameter), Expression(Of Func(Of Monster, Boolean)))
End Function
And the problematic method
Private Function BuildExpression(condition As Condition, ByVal dbType As Type, ByVal dbFieldMemberInfo As MemberInfo, ByVal predicate As Expression(Of Func(Of Monster, Boolean))) As Expression(Of Func(Of Monster, Boolean))
Dim type = condition.Value.GetType
Dim method As MethodInfo = type.GetMethod(condition.Operator, BindingFlags.Instance Or BindingFlags.Public, Nothing, {type}, Nothing)
If type = GetType(String) Then
If condition.Value.ToString.Contains(",") = True Then
Dim inner = PredicateBuilder.False(Of Product)()
For Each v In condition.Value.ToString.Split(",")
inner = inner.Or(CreateLambda(v, method, dbType, dbFieldMemberInfo))
Next
*****Return predicate.And(inner)*****
End If
End If
Return predicate.And(CreateLambda(condition.Value, method, dbType, dbFieldMemberInfo))
End Function
What is the problem?
The line in the last method surrounded by five asterisks is the issue.
When adding the OR grouped expression to the original predicate builder you get
The parameter 'f' was not bound in the specified LINQ to Entities
query expression.
For some reason, I can't seem to create a nested expression.
If you put the code above where the query is executed in usage sample, it works (after making some mods).
Update with solution:
The line with the asterisks needs to change to
Return predicate.And(inner.Expand)
I'm not a Visual Basic person, but in the C# implementation I think you'd probably need to add AsExpandable() to your query as per albahari.com/nutshell/predicatebuilder.aspx.
Related
I have 3 pages:
my DB-class
<!--#include virtual="/res/adovbs.inc"-->
<%
Class classDB
'Declarations
Private SQL
Private RS
Private Conn
'Class Initialization
Private Sub Class_Initialize()
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.ConnectionString = "DSN=x"
Call Conn.Open
End Sub
'Terminate Class
Private Sub Class_Terminate()
If IsObject(RS) Then
If RS.State = 1 Then
RS.Close()
End If
End If
If Conn.State = 1 Then
Conn.Close()
End If
End Sub
Public Function GetRS(SQL)
Set RS = Conn.Execute(SQL)
GetRS = RS
End Function
Public Function ExecuteSQL(SQL)
Conn.Execute(SQL)
End Function
End Class
%>
And my "functions":
<!--#include virtual="/res/classDB.asp"-->
<%
'GET PAGED MEMBERS
Function GetMembers(page)
If Not IsNumeric(page) Or page = "" Then page = 1
Dim db : Set db = new classDB
Set GetMembers = db.GetRS("SELECT * FROM member")
End Function
.....
And in my page:
<%
Dim members : Set members = GetMembers(1)
If Not members.EOF Then
Do Until members.EOF
%>
I get Error:
The object does not support the property or method 'EOF'
What might be wrong here? An Object was returned to the page. But I guess it has no properties or entries.... Impossible to debug.
EDIT:
Function GetMembers(page)
If Not IsNumeric(page) Or page = "" Then page = 1
Dim rs
Dim m : Set m = new classDB
Set rs = m.GetRS("SELECT * FROM member")
Response.Write(rs("email"))
Set GetMembers = rs
End Function
It prints out the email for the "member".... but it doesn't work on the "next" page with the loop.
It's it returning the recordset to the page?
<%
Dim m : Set m = GetMembers(1)
If Not m.EOF Then
Do Until m.EOF
%>
Is not working..... what to do?
EDIT
I changed to:
Public Function GetRS(SQL)
Dim RS : Set RS = m_Conn.Execute(SQL)
Set GetRS = RS
End Function
Now I get error:
"The action is not allowed when the object is closed"
Google translated :)
I have the open in the Class_Initialize.... why?
The first error is due to the lack of Set on the returned object reference in the GetRS() method in the classDB class. The fix for this is to change the return value so it is prefixed with Set like this;
Public Function GetRS(SQL)
Dim RS: Set RS = Conn.Execute(SQL)
'Returning object references from a function requires the "Set" statement.
Set GetRS = RS
End Function
You then reported a "not allowed when closed" error. This comes from the encapsulation of the ADODB.Connection object inside the classDB object instance. Because the connection is scoped to the class returning a recordset causes the connection to be severed, hence the recordset returning as closed.
The fix is to bring the connection outside the class so it lives in the context of the page and then passes it into the class via a parameter. There are varying ways of doing this, you could add a argument to the GetRS() method but this would only affect that single method. The other option is to build a sub procedure that you call after instantiation of the class to set the connection, something like;
Class classDB
'Declarations
Private SQL
Private RS
Private Conn
Public Sub Init(conn)
Set Conn = conn
End Sub
'Terminate Class
Private Sub Class_Terminate()
If IsObject(RS) Then
If RS.State = 1 Then
RS.Close()
End If
End If
If Conn.State = 1 Then
Conn.Close()
End If
End Sub
Public Function GetRS(SQL)
Set RS = Conn.Execute(SQL)
GetRS = RS
End Function
Public Function ExecuteSQL(SQL)
Conn.Execute(SQL)
End Function
End Class
You then make sure to call it before using the class and pass your ADODB.Connection object.
'Key is connection object is declared in the scope of the page
Dim conn: Set conn = Server.CreateObject("ADODB.Connection")
Call conn.Open("DSN=x")
'GET PAGED MEMBERS
Function GetMembers(page)
If Not IsNumeric(page) Or page = "" Then page = 1
Dim db : Set db = new classDB
'Call init() method and pass connection into the class instance.
Call db.Init(conn)
Set GetMembers = db.GetRS("SELECT * FROM member")
End Function
I have an Employee class where I call my database connection Module to make a query.
I am getting "Compile Error: Expected function or variable", which I'm not understanding, because I have the empObj set at the top of the form.
I would like to return the value from the Employee.getEmployee method. Can someone please show me how to call the Employee class from my form? Do I have to import the class first? I don't believe VB6 supports the Imports keyword.
This is my form:
Option Explicit
Private empObj As New Employee
Private Sub Form_Load()
'For testing only
MsgBox (empObj.getEmployee)
End Sub
This is my class:
Public Sub getEmployee()
'ConnectSQL is a database connection
return ConnectSQL
End Sub
And this is the module:
Public Function ConnectSQL()
Set SQLMyconn = New ADODB.Connection
Set SQLRecset = New ADODB.Recordset
SQLMyconn.Open "Driver={MySQL ODBC Client Interface};ServerName=localhost;dbq=#testdb"
End Function
The basic shell of what you want to do is like this:
Option Explicit
Private empObj As Employee
Private Sub Form_Load()
Set empObj = New Employee
MsgBox empObj.getEmployee
End Sub
Public Function getEmployee() As String
getEmployee = ConnectSQL
End Function
Public Function ConnectSQL() As String
Set SQLMyconn = New ADODB.Connection
Set SQLRecset = New ADODB.Recordset
SQLMyconn.Open "Driver={MySQL ODBC Client Interface};ServerName=localhost;dbq=#testdb"
ConnectSQL = "data from your DB lookup"
End Function
Almost every line is different than what you posted, so carefully look at the code.
EDIT:
Based on a comment, here's how to modify the code to return a connection object:
Option Explicit
Private empObj As Employee
Private Sub Form_Load()
Set empObj = New Employee
MsgBox empObj.getEmployee
Dim MyConnection As ADODB.Connection
Set MyConnection = ConnectSQL()
'you can grab and use the connection in your form, too.
End Sub
Public Function getEmployee() As String
Dim MyConnection As ADODB.Connection
Set MyConnection = ConnectSQL()
'use the connection to grab data
getEmployee = "data from your DB lookup"
End Function
Public Function ConnectSQL() As ADODB.Connection
Set ConnectSQL = New ADODB.Connection
ConnectSQL.Open "Driver={MySQL ODBC Client Interface};ServerName=localhost;dbq=#testdb"
End Function
Don't forget to close your connection after you are done with it. To sum up the changes:
empObj - you should declare and instantiate your objects separately.
MsgBox - no need for the ().
Functions vs Subs - the first returns data, the second does not. Make sure you declare the return type of a function.
return - this statement is obsolete and doesn't do what you want. Instead, assign a value to the name of the function.
I have a difficulties with subscription to EventAggregator, complete vb.net code is below.
'EventSystem' MODULE - the simplified PRISM from Rachel's blog, turned to VB.net as module as follows:
Imports Prism.Events
Module EventSystem
Private _current As IEventAggregator
Public ReadOnly Property Current As IEventAggregator
Get
#Disable Warning BC40000 ' Type or member is obsolete
Return If(_current, (CSharpImpl.__Assign(_current, New EventAggregator())))
#Enable Warning BC40000 ' Type or member is obsolete
End Get
End Property
Private Function GetEvent(Of TEvent)() As PubSubEvent(Of TEvent)
Return Current.GetEvent(Of PubSubEvent(Of TEvent))()
End Function
Sub Publish(Of TEvent)()
Publish(Of TEvent)(Nothing)
End Sub
Sub Publish(Of TEvent)(ByVal [event] As TEvent)
GetEvent(Of TEvent)().Publish([event])
End Sub
Function Subscribe(Of TEvent)(ByVal action As Action, ByVal Optional threadOption As ThreadOption = ThreadOption.PublisherThread, ByVal Optional keepSubscriberReferenceAlive As Boolean = False) As SubscriptionToken
Return Subscribe(Of TEvent)(Sub(e) action(), threadOption, keepSubscriberReferenceAlive)
End Function
Function Subscribe(Of TEvent)(ByVal action As Action(Of TEvent), ByVal Optional threadOption As ThreadOption = ThreadOption.PublisherThread, ByVal Optional keepSubscriberReferenceAlive As Boolean = False, ByVal Optional filter As Predicate(Of TEvent) = Nothing) As SubscriptionToken
Return GetEvent(Of TEvent)().Subscribe(action, threadOption, keepSubscriberReferenceAlive, filter)
End Function
Sub Unsubscribe(Of TEvent)(ByVal token As SubscriptionToken)
GetEvent(Of TEvent)().Unsubscribe(token)
End Sub
Sub Unsubscribe(Of TEvent)(ByVal subscriber As Action(Of TEvent))
GetEvent(Of TEvent)().Unsubscribe(subscriber)
End Sub
Private Class CSharpImpl
<Obsolete("Please refactor calling code to use normal Visual Basic assignment")>
Shared Function __Assign(Of T)(ByRef target As T, value As T) As T
target = value
Return value
End Function
End Class
End Module
I am able to 'Publish' to 'EventSystem' without problems, the code as follow where as message is used class 'NewMessage':
EventSystem.Publish(Of NewMessage)(New NewMessage With {.Msg = "Test"})
Difficulties with 'Subscription', code is below and unfortunately not works:
EventSystem.Subscribe(Of NewMessage)(AppNavigate)
Private Sub AppNavigate(ByVal msg As NewMessage)
MsgBox(msg.Msg)
End Sub
Error: Argument not specified for parameter 'msg' of AppNavigate...
This cannot understand, the class NewMessage has property msg. as below
Public Class NewMessage
Public Msg As String
End Class
Please, help. Thank you
Finally found solution for VB.NET, maybe will be useful for somebody. All above could be used to implement Simplified PRISM Event Aggregator, the 'Subscription' to be performed as follows using lambda expression:
EventSystem.Subscribe(Of NewMessage)(AppNavigate)
Private ReadOnly AppNavigate As Action(Of NewMessage) = Sub(ByVal msg As NewMessage)
MsgBox(msg.Msg)
End Sub
Dim xyzWkB As Excel.workbook
Set xyzWkB = setGetxyzObj(reportFile.Text)
populateListCombo (xyzWkB)
The function is defined as follows:
Public Function populateListCombo(activeWkB As Excel.workbook)
......
......
End Function
But in execution at the time of calling populateListCombo am getting the error "object does not support this property"
Here setGetxyzObj is defined as follows:
Public Function setGetxyzObj(Optional userInput As String) As Excel.workbook
Static xyz As New Excel.Application
Static xyzWkB As Excel.workbook
Set xyz = New Excel.Application
If Not (userInput = "") Then
Set xyzWkB = xyz.Workbooks.Open(userInput)
End If
Set setGetxyzObj = xyzWkB
End Function
I have also tried with populateListCombo (setGetxyzObj()), but still getting the same error.
I'm getting this exception thrown when I run my tests:
Test method OuvertureClasseur threw exception: System.Runtime.InteropServices.InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used...
though each of my test passes when ran individually.
Here's my tests code:
<TestClass()> _
Public Class FabriqueExcelTests
Private Shared _xl As ApplicationClass
<ClassInitialize()> _
Public Shared Sub MyClassInitialize(ByVal testContext As TestContext)
_xl = New ApplicationClass()
_xl.Visible = False
_xl.ScreenUpdating = False
_xl.DisplayAlerts = False
End Sub
<ClassCleanup()> _
Public Shared Sub MyClassCleanup()
_xl.Quit()
_xl = Nothing
End Sub
<TestMethod()> _
Public Sub ConstructeurParDefaut()
Dim gestionnaire As GestionnaireExcel = New GestionnaireExcel()
Assert.IsNotNull(gestionnaire)
Assert.IsInstanceOfType(gestionnaire, GetType(GestionnaireExcel))
End Sub
<TestMethod()>
Public Sub CreationDUnFichierExcel()
Dim fichier As String = "C:\Temp\CreationFichierExcel.xls"
Using xl As GestionnaireExcel = New GestionnaireExcel()
Dim classeur As Workbook = xl.CreerClasseur(fichier)
classeur.Close()
classeur = Nothing
End Using
Assert.IsTrue(File.Exists(fichier))
File.Delete(fichier)
End Sub
<TestMethod()> _
Public Sub OuvertureClasseur()
Dim fichier As String = "C:\Temp\OuvertureClasseur.xls"
_xl.Workbooks.Add(XlWBATemplate.xlWBATWorksheet)
_xl.ActiveWorkbook.SaveAs(fichier)
_xl.ActiveWorkbook.Saved = True
_xl.Workbooks.Close()
Using xl As GestionnaireExcel = New GestionnaireExcel()
Dim classeur As Workbook = xl.OuvrirClasseur(fichier)
Assert.AreEqual(fichier, classeur.FullName)
classeur.Close()
classeur = Nothing
End Using
File.Delete(fichier)
End Sub
End Class
And my GestionnaireExcel class:
Public Class GestionnaireExcel
Implements IDisposable
Private _cultureOriginale As CultureInfo
Private _disposedValue As Boolean = False '' To detect redundant calls
Private _excel As ApplicationClass
Public Sub New()
InitialiserExcel()
End Sub
'' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me._disposedValue Then
If disposing Then
'' TODO: free other state (managed objects).
If (_excel IsNot Nothing) Then
_excel.Quit()
_excel = Nothing
System.Threading.Thread.CurrentThread.CurrentCulture = _cultureOriginale
End If
End If
'' TODO: free your own state (unmanaged objects).
'' TODO: set large fields to null.
End If
Me._disposedValue = True
End Sub
'' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
'' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Public Function CreerClasseur(ByVal classeur As String) As Workbook
_excel.Workbooks.Add(XlWBATemplate.xlWBATWorksheet)
_excel.ActiveWorkbook.SaveAs(classeur)
Return _excel.ActiveWorkbook
End Function
Private Sub InitialiserExcel()
_cultureOriginale = System.Threading.Thread.CurrentThread.CurrentCulture
System.Threading.Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
_excel = New ApplicationClass()
_excel.DisplayAlerts = False
_excel.ScreenUpdating = False
_excel.Visible = False
End Sub
Public Function OuvrirClasseur(ByVal fichier As String) As Workbook
Dim classeur As Workbook = _excel.Workbooks.Open(fichier _
, False, False _
, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing _
, False _
, Type.Missing, Type.Missing _
, False _
, Type.Missing, Type.Missing)
Return classeur
End Function
End Class
Related questions found
Though I have read and understand these questions and answers, I don't seem to find what causes my OuvertureClasseur() test to fail when I run all my tests together, and it works just fine when I run it individually.
COM object that has been separated from its underlying RCW cannot be used.
WMI Exception: “COM object that has been separated from its underlying RCW cannot be used”
COM object that has been separated from its underlying RCW cannot be used.
COM object that has been separated from its underlying RCW can not be used - why does it happen?
WPF Thread: “COM object that has been separated from its underlying RCW cannot be used.”
I have been running around for few hours now, and any help will gladly be appreciated.
Thanks in advance! =)
Excel's executing as another process. It's possible that what's happening in classeur.Close() is asynchronous, and that some of it is taking place after the GestionnaireExcel instance is disposed.
Try commenting out the using block and see what happens.