Calling a user defined function in a LINQ query - vb.net

I've seen a few pages on the net regarding this, but unfortunately the sample code is in C#. I can't make sense of most of it (and/or run it through a code translator) but I still need help making it work in VB.
My custom function is:
Public Shared Function GetFriendlyTitle(Title As String) As String
Dim ConvertedTitle As String = ""
Try
Dim oRE As Regex = New Regex("[^a-zA-Z0-9\s]")
ConvertedTitle = Trim(oRE.Replace(Title, "")).Replace(" ", "_")
Catch ex As Exception
LogError(ex)
End Try
Return ConvertedTitle
End Function
and here's the function I'm calling to return products:
Public Shared Function GetProductByTypeAndTitle(URLFriendlyTitle As String, ProductType As ProductType)
Try
'don't know why, but the server threw errors if I went c.Type=Type
Dim pt As Integer = CInt([Enum].Parse(GetType(ProductType), [Enum].GetName(GetType(ProductType), ProductType)))
Dim context As LionsSharePressEntities = New LionsSharePressModel.LionsSharePressEntities
return From p In context.Products Where GetFriendlyTitle(p.Title) = URLFriendlyTitle AndAlso p.Type = pt
Catch ex As Exception
LogError(ex)
Return nothing
End Try
End Function
It compiles fine, but hangs when I run it. It's that return line that does it.

Try to add Select statement:
return From p In context.Products
Where GetFriendlyTitle(p.Title) = URLFriendlyTitle
AndAlso p.Type = pt
Select p

This question is a bit old and already seems to have been resolved. But I'm still postings this in hopes that maybe it will help someone out with an alternate solution that has worked for me.
Also please note that I was not in a position to make my function called from w/in my LINQ query "Shared" making my situation slightly different. Which I thought all the more reason to post an alternate answer.
Public Function GetFriendlyTitle(Title As String) As String
Dim ConvertedTitle As String = ""
Try
Dim oRE As Regex = New Regex("[^a-zA-Z0-9\s]")
ConvertedTitle = Trim(oRE.Replace(Title, "")).Replace(" ", "_")
Catch ex As Exception
LogError(ex)
End Try
Return ConvertedTitle
End Function
When I ran into an issue calling a non shared user defined function from w/in a LINQ query this is how I resolved it using the OPs code snippet as an example.
Public Shared Function GetProductByTypeAndTitle(URLFriendlyTitle As String, _
ProductType As ProductType)
Try
'don't know why, but the server threw errors if I went c.Type=Type
Dim pt As Integer = _
CInt([Enum].Parse(GetType(ProductType), _
[Enum].GetName(GetType(ProductType), ProductType)))
Dim context As New LionsSharePressModel.LionsSharePressEntities
'// Here is the lambda that will call your GetFriendlyTitle function
Dim callUserFunc As Func(Of String, String) = _
Function(title As String)
Return GetFriendlyTitle(title)
End Function
'// Now call Invoke on the lambda and pass in the needed param(s) from within your LINQ query
return From p In context.Products _
Where callUserFunc.Invoke(p.Title) = URLFriendlyTitle AndAlso p.Type = pt
'//return From p In context.Products Where GetFriendlyTitle(p.Title) = URLFriendlyTitle AndAlso p.Type = pt
Catch ex As Exception
LogError(ex)
Return nothing
End Try
End Function

Related

Use type to access properties instead of concrete instance

I'm having trouble figuring out how to ask this question (the title is not worded well), so let me start with an example using the NameOf() method, because it does something similar to what I'm trying to accomplish.
If I have a class like this:
Public Class MyClass
Public Property Foo As Bar
End Class
and then I declare an instance of it Dim instance As New MyClass, I can then use the NameOf() method in one of two ways:
' Both of these lines will print "Foo"
Console.WriteLine(NameOf(instance.Foo))
Console.WriteLine(NameOf(MyClass.Foo))
I would like to implement a method that can accept similar types of parameters, but I cannot figure out how.
Here is the (extension) method I wrote:
<Extension()>
Public Function GetPathOfProperty(Of T As Class, TProp)(myObj As T, prop As Expression(Of Func(Of TProp))) As String
Dim result As String = String.Empty
Dim foundName As String = String.Empty
Dim className As String = myObj.GetType().Name
Dim p As MemberExpression = TryCast(prop.Body, MemberExpression)
While p IsNot Nothing
If className = p.Member.DeclaringType.Name Then
foundName = className + "."
End If
result = foundName + p.Member.Name + "." + result
If Not String.IsNullOrEmpty(foundName) Then
Exit While
End If
p = TryCast(p.Expression, MemberExpression)
End While
Return IIf(String.IsNullOrEmpty(foundName), "", result.Substring(0, result.Length - 1))
End Function
Using my above example, I can use it like this:
' Prints "MyClass.Foo"
Console.WriteLine(instance.GetPathOfProperty(Function() instance.Foo))
' Prints "MyClass.Foo.SomeBarProperty"
Console.WriteLine(instance.GetPathOfProperty(Function() instance.Foo.SomeBarProperty))
I would like to create another version of this method that is not an extension method, but rather a static method that can be called like (or similar to) this:
Console.WriteLine(GetPathOfProperty(Function() MyClass.Foo) ' Prints "MyClass.Foo"
This way I'd be able to use the function without actually creating an instance of MyClass first. And this is where I need help. Since MyClass is not a static class, I'm not able to put MyClass.Foo in the function call. My first thought was to use Reflection, but I can't figure out how. Is there a parameter type I could use to allow me to do this (and if so, how would that look)? Or is this just a pipe-dream and not possible?
You can't pass in a direct reference to a property on a type, but you can cheat using lambda expressions (Function delegates) by typing the parameter to the delegate (essentially a static open instance delegate).
Here is my module to return a path reference from an instance or from a type:
Public Module PropertyExt
<Extension()>
Public Function TrimStart(Byval src As String, starter As String) As String
Return If(src.StartsWith(starter), src.Substring(starter.Length), src)
End Function
Public Function GetPathOfInstanceProperty(Of TRes)(e As Expression(Of Func(Of TRes))) As String
Dim b = TryCast(e.Body, MemberExpression)
Dim ans = ""
Do
ans = b.Member.Name.TrimStart("$VB$Local_") & "." & ans
b = TryCast(b.Expression, MemberExpression)
Loop While (b IsNot Nothing)
Return ans.TrimEnd(".")
End Function
Public Function GetPathOfTypeProperty(Of T,TRes)(e As Expression(Of Func(Of T,TRes))) As String
Dim b = TryCast(e.Body, MemberExpression)
Dim ans = ""
Do
ans = b.Member.Name.TrimStart("$VB$Local_") & "." & ans
Dim tryB = TryCast(b.Expression, MemberExpression)
If tryB Is Nothing Then
Dim tryParm = TryCast(b.Expression, ParameterExpression)
If tryParm IsNot Nothing Then
ans = tryParm.Type.Name & "." & ans
End If
End If
b = tryB
Loop While (b IsNot Nothing)
Return ans.TrimEnd(".")
End Function
End Module
Which you can use like so:
Dim cf = New CInstance()
Dim ipe = PropertyExt.GetPathOfInstanceProperty(Function() cf.Foo.SomeBarProperty)
Dim tpe = PropertyExt.GetPathOfTypeProperty(Function(i As CInstance) i.Foo.SomeBarProperty)
NOTE: VB also seems to rename local variables so I added some code to drop the $VB$Local_ prefix from names - this can be removed if not needed.
PS This seems a lot cleaner/simpler in C# ;)

No default member found for type '' when using Activator.Instance

I'm writing a project where this will be extended with further modules later on, and as such i want as much of the code as possible to be independent, so I'm using various methods to keep everything as generic as possible, and not require hard-coding lists of types, classes etc.
As a Proof of concept, I've come up with the following code in a class;
Public Class clsAddition
Public Function DoAction(Value1 As String, Value2 As String) As String
Dim dblTmpRet As Double
Dim strTmpRet As String = ""
If IsNumeric(Value1) = False Then strTmpRet += "Argument 'Value1' is not numeric. "
If IsNumeric(Value2) = False Then strTmpRet += "Argument 'Value2' is not numeric. "
If strTmpRet = "" Then
dblTmpRet = CDbl(Value1) + CDbl(Value2)
strTmpRet = CStr(dblTmpRet)
End If
Return strTmpRet
End Function
Private Function IsNumeric(Input As String) As Boolean
Dim blnTmpRet As Boolean
Try
Dim dblTest As Double = CDbl(Input)
blnTmpRet = True
Catch ex As Exception
blnTmpRet = False
End Try
Return blnTmpRet
End Function
Public Sub New()
MyBase.New()
End Sub
End Class
And the following code in a windows form;
Private objObjectClass As Object
Private Sub cmdCreateObject_Click(sender As Object, e As EventArgs) Handles cmdCreateObject.Click
Dim assem As Assembly = GetType(Form1).Assembly
Dim typChosen As Type
Try
typChosen = assem.GetType(Me.comObjectType.SelectedItem.ToString, True)
Catch ex As Exception
typChosen = Nothing
End Try
If Not IsNothing(typChosen) Then
'found it
objObjectClass = Activator.CreateInstance(typChosen, True)()
Else
'didn't find it
Throw New Exception("Unable to locate type in assembly")
End If
End Sub
The issue is, that on the "Activator.CreateInstance(typChosen, True)()" line, the following error is thrown
An unhandled exception of type 'System.MissingMemberException'
occurred in Microsoft.VisualBasic.dll Additional information: No
default member found for type 'clsAddition'
The aim is for me to be able to create instances of classes, then call a known name function on them. In this example, I'd create another class called "Divide", or another one that was called "Concatinate" etc, however in the real program these classes will all perform very different actions, have different properties, methods etc, with this one function ("DoAction" in the above example) sharing a syntax and return type.
Please help! been banging my head on this for a while now!
TIA!
As Per Hans Passant comment, this is due to the extra parenthesis on the following line;
Activator.CreateInstance(typChosen, True)()
Changing this to ;
Activator.CreateInstance(typChosen, True)
resolves the issue.

VB.NET Sub inside of a Function? What is this?

So I'm reading through my source code looking for places to improve the code when I come across this unholy chunk of code.
Public Function ReadPDFFile(filePath As String,
Optional maxLength As Integer = 0) As List(Of String)
Dim sbContents As New Text.StringBuilder
Dim cArrayType As Type = GetType(PdfSharp.Pdf.Content.Objects.CArray)
Dim cCommentType As Type = GetType(PdfSharp.Pdf.Content.Objects.CComment)
Dim cIntegerType As Type = GetType(PdfSharp.Pdf.Content.Objects.CInteger)
Dim cNameType As Type = GetType(PdfSharp.Pdf.Content.Objects.CName)
Dim cNumberType As Type = GetType(PdfSharp.Pdf.Content.Objects.CNumber)
Dim cOperatorType As Type = GetType(PdfSharp.Pdf.Content.Objects.COperator)
Dim cRealType As Type = GetType(PdfSharp.Pdf.Content.Objects.CReal)
Dim cSequenceType As Type = GetType(PdfSharp.Pdf.Content.Objects.CSequence)
Dim cStringType As Type = GetType(PdfSharp.Pdf.Content.Objects.CString)
Dim opCodeNameType As Type = GetType(PdfSharp.Pdf.Content.Objects.OpCodeName)
Dim ReadObject As Action(Of PdfSharp.Pdf.Content.Objects.CObject) = Sub(obj As PdfSharp.Pdf.Content.Objects.CObject)
Dim objType As Type = obj.GetType
Select Case objType
Case cArrayType
Dim arrObj As PdfSharp.Pdf.Content.Objects.CArray = DirectCast(obj, PdfSharp.Pdf.Content.Objects.CArray)
For Each member As PdfSharp.Pdf.Content.Objects.CObject In arrObj
ReadObject(member)
Next
Case cOperatorType
Dim opObj As PdfSharp.Pdf.Content.Objects.COperator = DirectCast(obj, PdfSharp.Pdf.Content.Objects.COperator)
Select Case System.Enum.GetName(opCodeNameType, opObj.OpCode.OpCodeName)
Case "ET", "Tx"
sbContents.Append(vbNewLine)
Case "Tj", "TJ"
For Each operand As PdfSharp.Pdf.Content.Objects.CObject In opObj.Operands
ReadObject(operand)
Next
Case "QuoteSingle", "QuoteDbl"
sbContents.Append(vbNewLine)
For Each operand As PdfSharp.Pdf.Content.Objects.CObject In opObj.Operands
ReadObject(operand)
Next
Case Else
'Do Nothing
End Select
Case cSequenceType
Dim seqObj As PdfSharp.Pdf.Content.Objects.CSequence = DirectCast(obj, PdfSharp.Pdf.Content.Objects.CSequence)
For Each member As PdfSharp.Pdf.Content.Objects.CObject In seqObj
ReadObject(member)
Next
Case cStringType
sbContents.Append(DirectCast(obj, PdfSharp.Pdf.Content.Objects.CString).Value)
Case cCommentType, cIntegerType, cNameType, cNumberType, cRealType
'Do Nothing
Case Else
Throw New NotImplementedException(obj.GetType().AssemblyQualifiedName)
End Select
End Sub
Using pd As PdfSharp.Pdf.PdfDocument = PdfSharp.Pdf.IO.PdfReader.Open(filePath, PdfSharp.Pdf.IO.PdfDocumentOpenMode.ReadOnly)
For Each page As PdfSharp.Pdf.PdfPage In pd.Pages
ReadObject(PdfSharp.Pdf.Content.ContentReader.ReadContent(page))
If maxLength > 0 And sbContents.Length >= maxLength Then
If sbContents.Length > maxLength Then
sbContents.Remove(maxLength - 1, sbContents.Length - maxLength)
End If
Exit For
End If
sbContents.Append(vbNewLine)
Next
End Using
'Return sbContents.ToString
Dim ReturnList As New List(Of String)
For Each Line In sbContents.ToString.Split(vbNewLine)
If String.IsNullOrWhiteSpace(Line.Trim) Then
Else
ReturnList.Add(Line.Trim)
End If
Next
Return ReturnList
End Function
All this does is read the text parts of a PDF using PDFSharp. What caught my eye however was line 17. Is that a Sub inside of the function?
So, what exactly is this Sub inside of a function? I didn't write this code so I've never seen anything like this before.
How does this work exactly and why wouldn't I use a function to do the processing and then return the results?
In short, my question is, what is this, how does it work, and why would I want to use something like this?
That's a so-called Lambda expression. They're used to create inline (or more correctly: in-method) methods, which makes them more dynamic than normal methods.
In your example a lambda expression is not necessary and only makes the code harder to understand. I suppose the author of that code wrote a lambda expression instead of a separate method in order to not expose ReadObject to any outside code.
One of the best uses for a lambda expression IMO is when you want to make thread-safe calls to the UI thread, for instance:
If Me.InvokeRequired = True Then
Me.Invoke(Sub() TextBox1.Text = "Process complete!")
Else
TextBox1.Text = "Process complete!"
End If
...where the same code without a lambda would look like this:
Delegate Sub UpdateStatusTextDelegate(ByVal Text As String)
...somewhere else...
If Me.InvokeRequired = True Then
Me.Invoke(New UpdateStatusTextDelegate(AddressOf UpdateStatusText), "Process complete!")
Else
UpdateStatusText("Process complete!")
End If
...end of somewhere else...
Private Sub UpdateStatusText(ByVal Text As String)
TextBox1.Text = Text
End Sub
There are also other examples where lambda expressions are useful, for instance if you want to initialize a variable but do some processing at first:
Public Class Globals
Public Shared ReadOnly Value As Integer = _
Function()
DoSomething()
Dim i As Double = CalculateSomething(3)
Return Math.Floor(3.45 * i)
End Function.Invoke()
...
End Class
Yet another usage example is for creating partially dynamic event handlers, like this answer of mine.

Too many arguments to '' in VB.net

I'm trying to save from list field that have more than one data on it, how to save all of it at once?
I've try this code
Dim detail As New Detail
Dim detailBr As New DetailBridge
Dim i As Integer
For i = 0 To lstProduct.Items.Count - 1
detail = detailBr.Insert(Convert.ToInt32(ddlGroup.SelectedValue), lstProduct.Items(i).Value) 'error was here
Next
but I got an error in lstProduct.Items(i).Value the error said
Too many arguments to '...'
I'm not sure what the error is.
can anyone help? Thanks for advice.
UPDATE : detailBr is class and the code is
Public Function Insert(ByVal GroupID As Integer, ByVal ProductID As String) As Boolean
Dim iResult As Integer
Dim arrColumn() As String = {"GroupID", "ProductID"}
Dim arrValue() As Object = {GroupID, ProductID}
oConn.Open()
Dim SQLString As String = GenInsert("DetailGroup", arrColumn, arrValue)
Try
iResult = SCommand.Execute(SQLString, oConn)
Catch ex As Exception
Throw ex
Finally
oConn.Close()
End Try
If iResult > 0 Then
Return True
Else
Return False
End If
End Function
The problem here is with the GenInsert Function. Its last two arguments are arrays.
Dim arrColumn() As String = {"GroupID", "ProductID"}
Dim arrValue() As Object = {GroupID, ProductID}
Dim SQLString As String = GenInsert("DetailGroup", arrColumn, arrValue)
A procedure can define only one parameter array, and it must be the last parameter in the procedure definition. MSDN
In simple words you can have only one parameter as array in GenInsert function either arrColumn or arrValue
However, to solve your current problem you can use two dimensional array as parameter as in passing-two-dimensional-array-through-functions and MSDN: Arrays as Return Values and Parameters

Linq Statement In vb

I am new to vb well not new but used chsarp for years I cant figure out what's wrong with this statement. The debugger is not even hitting the return list part so I don't no what is up. I am using the same context to add files so i don't think its the way i declared it
Private threeSoftwareContext As New R3DeliveryEntities1
Public Function GetAlLFilesToBeProcessedByLocation(location As Int32, status As StatusTypes) As List(Of downloadedFile)
Dim list As New List(Of downloadedFile)
list = (From files In threeSoftwareContext.downloadedFiles Where files.location = 0 And files.status = status).ToList()
Return list
End Function
Exception message
i got it working with this but what i dont no in vb is how do I give it columns names
Dim retVal As List(Of downloadedFile)
Try
retVal = (From files In threeSoftwareContext.downloadedFiles Where files.location = 0).ToList()
Return retVal
Catch ex As Exception
MsgBox(ex.ToString())
End Try
Return retVal