How build dynamic where clause with string argument - vb.net

I try to buid a method to add a where clause to a Linq-to-SQL request (return an IQueryable). I try several methods but always use ToString, Indexof... but this result is a sql request take all element and the filter made in linq. I see request in SQL Server profiler.
I want a method to do it with result is a sql request with where include inside
I work in Visual Studio 2017 with SQL Server 2016. I code in vb.net
I see an interesting thing in linq dynamic library. But I can't to adapt to my situation
<Extension()> _
Public Function Where(ByVal source As IQueryable, ByVal predicate As String, ByVal ParamArray values() As Object) As IQueryable
If source Is Nothing Then Throw New ArgumentNullException("source")
If predicate Is Nothing Then Throw New ArgumentNullException("predicate")
Dim lambda As LambdaExpression = DynamicExpression.ParseLambda(source.ElementType, GetType(Boolean), predicate, values)
Return source.Provider.CreateQuery( _
Expression.Call( _
GetType(Queryable), "Where", _
New Type() {source.ElementType}, _
source.Expression, Expression.Quote(lambda)))
End Function
But I don't need all this complex strucutre. It's some years I buid my utilities. But Need to upgrade it. Here my code of my utilities
<Extension()>
Public Function Where(ByVal source As IQueryable, ByVal predicate As String) As IQueryable
Dim param = Expression.Parameter(GetType(String), "x")
Return source.Provider.CreateQuery(
Expression.Call(
GetType(Queryable), "Where",
New Type() {source.ElementType},
source.Expression, Expression.Quote(Expression.Lambda(Expression.Constant(predicate), param))))
End Function
Public Function TFOAppliqueFiltreTri(Of T, MaClassDatas As Class)(Origins As IQueryable(Of T), ByVal MesDonnees As TableFullOption.PagerTabEnCours(Of MaClassDatas)) As IQueryable(of T)
Dim retour As New TableFullOption.LstRetour
'Colonne de filtre
Dim strWh As String = ""
Dim Filtredrecords As IQueryable(Of T)
For Each Sort In MesDonnees.MesOptions
Dim colName = Sort.ColName
If strWh.Length > 0 Then strWh = strWh & " AND "
strWh = strWh & String.Format(colName & " like '%{0}%'", Sort.Search)
Next
If strWh.Length > 0 Then
Filtredrecords = Origins.Where(strWh) '<- Here call Where
Else
Filtredrecords = Origins
End If
Return Filtredrecords
End Function
I get this error:
Aucune méthode générique 'Where' sur le type 'System.Linq.Queryable' n'est compatible avec les arguments de type et les arguments fournis..
Then my problem is to write correctly lambda expression. My predicate argument is : Column1 like '%aaa%'. I want rewrite where method of dynamicLinq to accept string argument :Column1 like '%aaa%' directly
Thanks for your help

Finally after lot of reading in google and few feelings and certainly lot of chance.
Public Function Where(Of TEntity)(source As IQueryable(Of TEntity), searchColumn As List(Of String), searchValue As String) As IQueryable(Of TEntity)
Dim cond As Expression = Nothing
Dim ParamExpr = Expression.Parameter(GetType(TEntity), "x")
Dim conCat2 = GetType(String).GetMethod("Concat", New Type() {GetType(String), GetType(String)})
Dim conCat4 = GetType(String).GetMethod("Concat", New Type() {GetType(String), GetType(String), GetType(String), GetType(String)})
Dim Delim = Expression.Constant("/")
Dim DateName = GetType(SqlFunctions).GetMethod("DateName", New Type() {GetType(String), GetType(Nullable(Of DateTime))})
Dim DatePart = GetType(SqlFunctions).GetMethod("DatePart", New Type() {GetType(String), GetType(Nullable(Of DateTime))})
Dim DblToString = GetType(SqlFunctions).GetMethod("StringConvert", New Type() {GetType(Nullable(Of Double))})
For Each cn In searchColumn
For Each colName In cn.Split("|")
If Not colName.estVide Then
Dim body As Expression = ParamExpr
For Each member In colName.Split(".")
body = Expression.PropertyOrField(body, member)
Next
Dim Tostr As Expression
If body.Type.FullName.Contains("String") Then
Tostr = body
ElseIf body.Type.FullName.Contains("DateTime") Then
Dim day = Expression.Call(Expression.Call(conCat2, Expression.Constant("0"), Expression.Call(DateName, Expression.Constant("day"), body)), "Substring", Nothing, Expression.Constant(0), Expression.Constant(2))
Dim Month = Expression.Call(DatePart, Expression.Constant("MM"), body)
Dim toDouble = Expression.Convert(Month, GetType(Nullable(Of Double)))
Dim mois = Expression.Call(conCat2, Expression.Constant("0"), Expression.Call(Expression.Call(DblToString, toDouble), "Trim", Nothing))
Dim an = Expression.Call(DateName, Expression.Constant("year"), body)
Tostr = Expression.Call(conCat2, Expression.Call(conCat4, day, Delim, mois, Delim), an)
Else
Tostr = Expression.Call(body, "Convert.ToString", Nothing)
'Tostr = Expression.Convert(body, GetType(String))
End If
Dim condPart = Expression.Call(Expression.Call(Tostr, "ToLower", Nothing), "Contains", Nothing, Expression.Call(Expression.Constant(searchValue), "ToLower", Nothing))
If cond Is Nothing Then
cond = condPart
Else
cond = Expression.OrElse(cond, condPart)
End If
End If
Next
Next
Return source.Provider.CreateQuery(Of TEntity)(Expression.Call(GetType(Queryable), "Where", New Type() {GetType(TEntity)}, source.Expression, Expression.Lambda(cond, ParamExpr)))
End Function
Now I've dynamic filter which generated a SQL request with complete clause where

Related

How to factorize calls to ado.net with parameters?

i want to factorize all the calls to ado.net present in my web application to not repeat over and over the connection string and the open/close methods. I succeed to do it for the calls without parameter, but i need help for the ones with parameters.
For example, I had :
Dim strConnexion As String = "myConnectionString"
Dim strRequete As String = "DELETE FROM tbl_devis WHERE id_devis = " + TBDevis.Text
Dim oConnection As New SqlConnection(strConnexion)
Dim oCommand As New SqlCommand(strRequete, oConnection)
oConnection.Open()
oConnection.ExecuteNonQuery()
oConnection.Close()
I factorized it into :
ExecuteRequest("DELETE FROM tbl_devis WHERE id_devis = " + TBDevis.Text)
And the code of ExecuteRequest :
Public Shared Sub ExecuteRequest(ByVal strRequest As String)
Dim strConnection As String = ChaineDeConnexion()
Using objConnection = New SqlConnection(strConnection)
Dim objCommand As SqlCommand
objCommand = New SqlCommand(strRequest, objConnection)
objCommand.Connection.Open()
objCommand.ExecuteNonQuery()
End Using
End Sub
But I would like be able to pass to Execute request a collection of parameters. This is a very simple example of what kind of code I want to factorize :
Dim strConnexion As String = "myConnectionString"
Dim strRequete As String = "DELETE FROM tbl_devis WHERE id_devis = #id_devis"
Dim oConnection As New SqlConnection(strConnexion)
Dim oCommand As New SqlCommand(strRequete, oConnection)
With (myCommand.Parameters)
.Add(New SqlParameter("#id_devis", SqlDbType.Int))
End With
With myCommand
.Parameters("#id_devis").Value = TBDevis.Text
End With
oConnection.Open()
oConnection.ExecuteNonQuery()
oConnection.Close()
I was thinking about edit my ExecuteRequest function to add an optional parameters collection :
Public Shared Sub ExecuteRequest(ByVal strRequest As String, Optional ByRef sqlParameters As SqlParameterCollection = Nothing)
Dim strConnection As String = ChaineDeConnexion()
Using objConnection = New SqlConnection(strConnection)
Dim objCommand As SqlCommand
objCommand = New SqlCommand(strRequest, objConnection)
objCommand.Parameters = sqlParameters 'objCommand.Parameters is readonly property
objCommand.Connection.Open()
objCommand.ExecuteNonQuery()
End Using
End Sub
But VS tell me that objCommand.Parameters is a readonly property...
I see two solutions :
Passing an array containing the parameter name, value and type, and looping through the array
Creating the string request with all the parameters like that : "DELETE FROM tbl_devis WHERE id_devis = " + TBDevis.Text ... but when there are 30 parameters, this is a dirty solution I guess ?
Which one would be the cleaner, strongest solution please ?
Thanks for your help !
ParamArray is what you're looking for.
Update your ExecuteRequest like this:
Public Sub ExecuteRequest(ByVal strRequest As String, ParamArray Params() As SqlParameter)
Dim strConnexion As String = "myConnectionString"
Using Conn As New SqlConnection(strConnexion), Cmd As New SqlCommand(strRequest, Conn)
Cmd.Parameters.AddRange(Params)
Conn.Open()
Cmd.ExecuteNonQuery()
End Using
End Sub
and then you can call it like
ExecuteRequest("DELETE FROM tbl_devis WHERE id_devis = #id_devis", New SqlParameter("#id_devis", CInt(TBDevis.Text)))
I would also suggest to create function sqlPar(Name As String, Value As Object) with few more overloads to simplify the call to
ExecuteRequest("DELETE FROM tbl_devis WHERE id_devis = #id_devis", sqlPar("#id_devis", TBDevis.Text))
ParamArray allows you to add undefined amount of arguments like this
ExecuteRequest("SELECT ID FROM Table WHERE ID IN (#A, #B, #C, #D)", sqlPar("#A", 1), sqlPar("#B", 2), sqlPar("#C", 3), sqlPar("#D", 4))
You should ALWAYS use SqlParameter instead of string concatenation to prevent SQL injections.
You should ALWAYS use Using for IDisposable resources as well.

Can you add an If Statement in a LINQ(XML) query?

I feel this function is too long. I can separate it into two functions, but I would prefer to keep them in one for this.
Public Function getTempList(ByVal applicationType As String) As List(Of String)
Dim doc As XDocument = New XDocument
If My.Settings.sortKey = "alpha" Then
Dim XMLquery = From c In doc.<applications>.<app> _
Where c.<appFav>.Value = "true" And c.<appType>.Value = applicationType
Order By CStr(c.<appName>.Value)
Select c.<appName>
Dim tempList As New List(Of String)
For Each result In XMLquery
tempList.Add(result.Value)
Next
Return tempList
ElseIf My.Settings.sortKey = "fav" Then ' ------------------------------------------------------------------------------------
Dim XMLquery = From c In doc.<applications>.<app> _
Where c.<appFav>.Value = "true" And c.<appType>.Value = applicationType
Order By CInt(c.<appClick>.Value) Descending
Select c.<appName>
Dim tempList As New List(Of String)
For Each result In XMLquery
tempList.Add(result.Value)
Next
Return tempList
End If
End Function
Can I somehow put the if statement in the LINQ query itself. The only thing that needs to change here is the order the list is in. Or, is there another way to order the results I am returning ?
I think this would be the simplest:
Public Function getTempList(ByVal applicationType As String) As List(Of String)
Dim doc As XDocument = New XDocument
Dim XMLquery = _
From c In doc.<applications>.<app> _
Where c.<appFav>.Value = "true" And c.<appType>.Value = applicationType _
Select c
If My.Settings.sortKey = "alpha" Then
XMLquery = XMLquery.OrderBy(Function(c) CStr(c.<appName>.Value))
ElseIf My.Settings.sortKey = "fav" Then
XMLquery = XMLquery.OrderByDescending(Function(c) CInt(c.<appClick>.Value))
End If
Return XMLquery.Select(Function(x) x.<appName>.Value).ToList()
End Function
Try this,
Public Function getTempList(ByVal applicationType As String) As List(Of String)
Dim doc As XDocument = New XDocument
Dim XMLquery = From c In doc.<applications>.<app> _
Where c.<appFav>.Value = "true" And c.<appType>.Value = applicationType
Select c
Dim tempList As New List(Of String)
If My.Settings.sortKey = "alpha" Then
XMLquery = XMLquery.OrderBy(Function(c) CStr(c.<appName>.Value))
ElseIf My.Settings.sortKey = "fav" Then
XMLquery = XMLquery.OrderByDescending(Function(c) CInt(c.<appClick>.Value))
End If
For Each result In XMLquery
tempList.Add(result.<appName>.Value)
Next
Return tempList
End Function
You dont need to repeat the whole query.
You can do something like:
Pardon me for VB syntax, I usually code in C#.
Public Function getTempList(ByVal applicationType As String) As List(Of String)
Dim doc As XDocument = New XDocument
Dim tempList As New List(Of String)
Dim XMLquery = From c In doc.<applications>.<app> _
Where c.<appFav>.Value = "true" And c.<appType>.Value = applicationType
Select c.<appName>
If My.Settings.sortKey = "aplha" Then
Order XMLQuery By CStr(c.<appName>.Value) // convert to VB code
If My.Settings.sortKey = "fav" Then
Order XMLQuery By CInt(c.<appClick>.Value) Descending //convert to VB code
For Each result In XMLquery
tempList.Add(result.Value)
Next
Return tempList
End Function

vb.net List of dynamic type

I'm building a MVC structure for a part of my program.
I've done the Models of 5-10 tables and what they have in common is only the constructor. (which takes the recordset.fields)
Here's my function to fill these objects:
Public Function reqTable(ByVal pTable As String, ByVal pType As Type, ByVal pNoProjet As Integer, Optional ByVal strAdditionnalConditions As String = "") As List(Of Object)
Dim lstRetour As List(Of Object) = New List(Of Object)
rsRequestCSV = conSQL.Execute("SELECT * FROM " & pTable & " WHERE NoProjet = " & pNoProjet & " " & strAdditionnalConditions)
With rsRequestCSV
While Not .EOF
lstRetour.Add(Activator.CreateInstance(pType, New Object() {rsRequestCSV.Fields})) 'New clsTable(rsRequestCSV.Fields))
.MoveNext()
End While
End With
Return lstRetour
End Function
What I'm not able to achieve is to return a List(Of pType) instead of List(Of Object).
The reason I want this is to have headers in my datagridviews even if they're empty.
So is there a way to return a List(Of MyModel'sType) ?
thanks in advance!
Just use As pType instead of As Object (but consider using a conventional type argument name, i.e. T instead of pType), remove the now obsolete pType argument, and use the following to create and add the instances:
Public Function ReqTable(Of T)(ByVal table As String, ByVal noProject As Integer, Optional ByVal additionalConditions As String = "") As List(Of T)
Dim result As New List(Of T)()
' Where is this declared?! It probably should be declared here.
request = conSQL.Execute( _
String.Format("SELECT * FROM {0} WHERE NoProjet = {1} {2}", _
table, noProjet, additionnalConditions))
While Not request.EOF
result.Add( _
CType(Activator.CreateInstance( _
GetType(T), New Object() {request.Fields}), _
T))
request.MoveNext()
End While
Return result
End Function
GetTpe(T) gets you a System.Type instance representing the type argument. Since VB, unlike Java, has reified generic types you can thus create instances from type argument.
Apart from that, pay attention that .NET has different code style conventions than Java; for instance, all methods should use PascalCase, not camelCase. And like in Java, use of Hungarian notation is discouraged. Use concise but descriptive names. And, as Rene noted, your code suffers from an SQL injection vulnerability.
Ignoring the SQL injection issue ect try this:
Public Function reqTable(of T)(ByVal pTable As String, ByVal pNoProjet As Integer, Optional ByVal strAdditionnalConditions As String = "") As List(Of T)
Dim lstRetour As New List(Of T)
rsRequestCSV = conSQL.Execute("SELECT * FROM " & pTable & " WHERE NoProjet = " & pNoProjet & " " & strAdditionnalConditions)
With rsRequestCSV
While Not .EOF
lstRetour.Add(Activator.CreateInstance(T, New Object() {rsRequestCSV.Fields})) 'New clsTable(rsRequestCSV.Fields))
.MoveNext()
End While
End With
Return lstRetour
End Function

Problem Understanding Access Modifiers in VB.Net with List( Of Object)

I've recently been updating a lot of my code to comply with proper n-tier architecture and OO programming, following examples from a book.
I'm starting to get problems now because I don't fully understand the access modifiers.
If I run the following code I get an error at the line
Dim clientFamilyDataAccessLayer As New ClientFamilyDAO
in the BLL at the point it creates an instance of the DAL. The full error message is: "The type initializer for 'ClientFamilyDAO' threw an exception. ---> System.NullReferenceException: Object reference not set to an instance of an object."
How do I use these function to create a list of ClientFamily objects that I can then work with?
On my UI layer I'm creating a list of objects; ClientFamilies
Dim listOfClientFamilies As List(Of ClientFamily) = ClientFamily.GetClientFamiliesByKRM(selectedEmployee.StaffNumber)
This is the function in the BLL
Public Shared Function GetClientFamiliesByKRM(ByVal krmStaffNumber As Integer) As List(Of ClientFamily)
Dim clientFamilyDataAccessLayer As New ClientFamilyDAO
Return clientFamilyDataAccessLayer.GetClientFamiliesByKRM(krmStaffNumber)
End Function
and this is function in the DAL
Public Function GetClientFamiliesByKRM(ByVal staffNumber As Integer) As List(Of ClientFamily)
Dim currentConnection As SqlConnection = New SqlConnection(_connectionString)
Dim currentCommand As New SqlCommand
currentCommand.CommandText = mainSelectStatement & " WHERE Key_Relationship_Manager = #StaffNumber ORDER BY Client_Family_Name"
currentCommand.Parameters.AddWithValue("#StaffNumber", staffNumber)
currentCommand.Connection = currentConnection
Dim listOfClientFamilies As New List(Of ClientFamily)
Using currentConnection
currentConnection.Open()
Dim currentDataReader As SqlDataReader = currentCommand.ExecuteReader()
Do While currentDataReader.Read
Dim newClientFamily As AECOM.ClientFamily = PopulateClientFamily(currentDataReader)
listOfClientFamilies.Add(newClientFamily)
Loop
End Using
Return listOfClientFamilies
End Function
Here's the full ClientFamilyDAO Class:
Public Class ClientFamilyDAO
Private Const mainSelectStatement As String = "SELECT Client_Family_ID, Client_Family_Name, Key_Relationship_Organisation, Key_Relationship_Manager, Obsolete, Market_Sector_ID FROM Client_Families"
Private Shared ReadOnly _connectionString As String = String.Empty
Shared Sub New()
_connectionString = WebConfigurationManager.ConnectionStrings("ClientFamilyManagementConnectionString").ConnectionString
End Sub
Public Function GetClientFamiliesByKRM(ByVal staffNumber As Integer) As List(Of ClientFamily)
Dim currentConnection As SqlConnection = New SqlConnection(_connectionString)
Dim currentCommand As New SqlCommand
currentCommand.CommandText = mainSelectStatement & " WHERE Key_Relationship_Manager = #StaffNumber ORDER BY Client_Family_Name"
currentCommand.Parameters.AddWithValue("#StaffNumber", staffNumber)
currentCommand.Connection = currentConnection
Dim listOfClientFamilies As New List(Of ClientFamily)
Using currentConnection
currentConnection.Open()
Dim currentDataReader As SqlDataReader = currentCommand.ExecuteReader()
Do While currentDataReader.Read
Dim newClientFamily As AECOM.ClientFamily = PopulateClientFamily(currentDataReader)
listOfClientFamilies.Add(newClientFamily)
Loop
End Using
Return listOfClientFamilies
End Function
Private Function PopulateClientFamily(ByVal currentDataReader As SqlDataReader) As AECOM.ClientFamily
Dim newClientFamily As New AECOM.ClientFamily
If Not (currentDataReader.IsDBNull(currentDataReader.GetOrdinal("Client_Family_ID"))) Then
newClientFamily.ClientFamilyID = currentDataReader("Client_Family_ID")
End If
If Not (currentDataReader.IsDBNull(currentDataReader.GetOrdinal("Client_Family_Name"))) Then
newClientFamily.ClientFamilyName = currentDataReader("Client_Family_Name")
End If
If Not (currentDataReader.IsDBNull(currentDataReader.GetOrdinal("Key_Relationship_Organisation"))) Then
Select Case currentDataReader("Key_Relationship_Organisation")
Case False
newClientFamily.IsKeyRelationshipOrganisation = False
Case True
newClientFamily.IsKeyRelationshipOrganisation = True
End Select
End If
If Not (currentDataReader.IsDBNull(currentDataReader.GetOrdinal("Key_Relationship_Manager"))) Then
newClientFamily.KeyRelationshipManagerStaffNumber = currentDataReader("Key_Relationship_Manager")
End If
If Not (currentDataReader.IsDBNull(currentDataReader.GetOrdinal("Obsolete"))) Then
Select Case currentDataReader("Obsolete")
Case False
newClientFamily.Obsolete = False
Case True
newClientFamily.Obsolete = True
End Select
End If
If Not (currentDataReader.IsDBNull(currentDataReader.GetOrdinal("Market_Sector_ID"))) Then
newClientFamily.MarketSectorID = currentDataReader("Market_Sector_ID")
End If
Return newClientFamily
End Function
End Class
The issue doesn't relate to access modifiers, rather it is more to do with the exception message you get. The following line within the constructor of ClientFamilyDAO would seem to be causing the issue:
_connectionString = WebConfigurationManager.ConnectionStrings("ClientFamilyManagementConnectionString").ConnectionString
Are you sure ClientFamilyManagementConnectionString exists in the configuration?

How do I invoke HasValue on a nullable property of an object via reflection?

This function loops all properties of an object to create the updatequery to save te object to the DB.
We had to make some changes to it because of the introduction of nullable properties.
If the property is nullable we would like to check the 'HasValue' property.
This does works when it has a value. When the property has no value we get an 'Non-static method requires a target'-error at the CBool-line
Any suggestions?
An other way to check the 'HasValue'-prop of a property using reflection?
Thanks.
Private Function GetUpdateQuery(ByVal obj As Object, ByRef params As List(Of SqlParameter), Optional ByVal excl As String() = Nothing) As String
Dim sql As String = String.Empty
Dim props As PropertyInfo() = obj.GetType().GetProperties
If excl Is Nothing Then
excl = New String() {}
End If
For Each prop As PropertyInfo In props
Try
If Not excl.Contains(prop.Name) And prop.CanWrite = True Then
sql &= String.Format("{0} = #{1},", prop.Name, prop.Name)
Dim param As SqlParameter
Dim value As Object
If prop.PropertyType.IsGenericType AndAlso prop.PropertyType.GetGenericTypeDefinition() = GetType(Nullable(Of )) Then
If CBool(prop.PropertyType.GetProperty("HasValue").GetValue(prop.GetValue(obj, Nothing), Nothing)) Then
value = prop.GetValue(obj, Nothing)
Else
value = DBNull.Value
End If
Else
If prop.GetValue(obj, Nothing) = Nothing Then
value = DBNull.Value
Else
value = prop.GetValue(obj, Nothing)
End If
End If
param = ConnSql.CreateParameter("#" & prop.Name, value)
params.Add(param)
End If
Catch ex As Exception
End Try
Next
sql = sql.Substring(0, sql.Length - 1)
Return sql
End Function
You do not need the following If. You can remove it.
If prop.PropertyType.IsGenericType AndAlso prop.PropertyType.GetGenericTypeDefinition() = GetType(Nullable(Of )) Then
BUT you do need to fix the following If:
If prop.GetValue(obj, Nothing) = Nothing Then
to
If prop.GetValue(obj, Nothing) IS Nothing Then
--
Complete code:
Private Function GetUpdateQuery(ByVal obj As Object, ByRef params As List(Of SqlParameter), Optional ByVal excl As String() = Nothing) As String
Dim sql As String = String.Empty
Dim props As PropertyInfo() = obj.GetType().GetProperties
If excl Is Nothing Then
excl = New String() {}
End If
For Each prop As PropertyInfo In props
If Not excl.Contains(prop.Name) And prop.CanWrite = True Then
sql &= String.Format("{0} = #{1},", prop.Name, prop.Name)
Dim param As SqlParameter
Dim value As Object
If prop.GetValue(obj, Nothing) Is Nothing Then
value = DBNull.Value
Else
value = prop.GetValue(obj, Nothing)
End If
param = ConnSql.CreateParameter("#" & prop.Name, value)
params.Add(param)
End If
Next
sql = sql.Substring(0, sql.Length - 1)
Return sql
End Function