Custom function(s) in LINQ to Entities? How to write acceptable code? - vb.net

Use of my simple function causes app to exit all the nested Using blocks and returns control to very outer End Using statement. But built-in function works fine. How to bypass this strange situation?
Public Shared ReadOnly GlobalSettingsKeyList As New HashSet(Of String)
Public Shared Function IsGlobalSetting(key As String) As Boolean
Return GlobalSettingsKeyList.Contains(key)
End Function
What doesn't work:
Using db = powerEntities.Open()
dim userID = 1
dim dbSettingsFound = (From setting In db.SETTINGS
Where setting.idUsers = If(IsGlobalSetting(setting.Name), Nothing, userID)
Where setting.Name.Contains(keyToMatch)) _
.ToDictionary(Function(x) x.Name, Function(y) y.Value)
End Using
What works fine:
Using db = powerEntities.Open()
dim userID = 1
dim dbSettingsFound = (From setting In db.SETTINGS
Where setting.idUsers = If(GlobalSettingsKeyList.Contains(setting.Name), Nothing, userID)
Where setting.Name.Contains(keyToMatch)) _
.ToDictionary(Function(x) x.Name, Function(y) y.Value)
End Using
{"LINQ to Entities does not recognize the method 'Boolean IsGlobalSetting(System.String)' method, and this method cannot be translated into a store expression."}

Done! Thanks to Jarekczek's answer here and his LinqExprHelper
So there is a way!
Private Shared Function GetDefaultKey(key As String) As String
If key.Contains("Station") Then
Return $"default{key.Substring("Station")}" 'my String Extension
Else
Return $"default{key}"
End If
End Function
Public Shared Function GetDefaultKeyAndCompareSetting(ByVal key As String) As Expression(Of Func(Of Settings, Boolean))
key = GetDefaultKey(key)
Return LinqExprHelper.NewExpr(Function(u As Settings) u.Name.Equals(key))
End Function
used like this...
Shared Sub Example(key As String)
...
Dim masterExpr = LinqExprHelper.NewExpr(Function(u As Settings, ByVal formatCompare As String) (formatCompare))
Dim isSameAsDefKey = masterExpr.ReplacePar("formatCompare", GetDefaultKeyAndCompareSetting(key).Body)
Dim result = (From def In db.Settings
Where def.idUsers Is Nothing).
Where(CType(isSameAsDefKey, Expression(Of Func(Of Settings, Boolean)))).FirstOrDefault
...
End Sub
...works like a charm

Related

Creating an object in VB.NET behaves differently on different SQL servers

I have following code which is behaving differently on different servers. In the below method if i write this line of code:
Dim customerPositionsFromPaid = vwCustomerPositionInPaid.SelectAll().Where(conditions).Select(Function(o) New CustomerPositionFromPaidDto(o.FundingYearId.Value, o.DsoId.Value, o.CustomerId.Value, o.CustomerPosition.Value)).ToList()
it returns result on one sql instance but does not return result on other sql instance.
However if i replace the above line with the following, it returns result on both sql instances.
Dim customerPositionsFromPaid = vwCustomerPositionInPaid.
SelectAll().
Where(conditions).
Select(Function(o) New CustomerPositionFromPaidDto() With {.FundingYearId = o.FundingYearId.Value, .DsoId = o.DsoId.Value, .CustomerId = o.CustomerId.Value, .CustomerPosition = o.CustomerPosition.Value}).
ToList()
Could it be because sql server instances have different settings or it's something to do with the code itself?
--Function
Private Shared Function GetCustomerPositionsFromPaid(ByVal customerID As Integer, ByVal fundingYearID As Integer) As IEnumerable(Of CustomerPositionFromPaidDto)
Dim conditions = PredicateBuilder.True(Of vwCustomerPositionInPaid)()
conditions = conditions.And(Function(o) o.CustomerId.Equals(customerID))
conditions = conditions.And(Function(o) o.FundingYearId.Equals(fundingYearID))
conditions = conditions.And(Function(o) o.DsoId.HasValue)
'Dim customerPositionsFromPaid = vwCustomerPositionInPaid.SelectAll().Where(conditions).Select(Function(o) New CustomerPositionFromPaidDto(o.FundingYearId.Value, o.DsoId.Value, o.CustomerId.Value, o.CustomerPosition.Value)).ToList()
'Dim customerPositionsFromPaid = vwCustomerPositionInPaid.SelectAll().Where(conditions).Select(Function(o) New With {.FundingYearId = o.FundingYearId.Value, .DsoId = o.DsoId.Value, .CustomerId = o.CustomerId.Value, .CustomerPosition = o.CustomerPosition.Value}).ToList().Select(Function(o) New CustomerPositionFromPaidDto(o.FundingYearId, o.DsoId, o.CustomerId, o.CustomerPosition)).ToList()
Dim customerPositionsFromPaid = vwCustomerPositionInPaid.
SelectAll().
Where(conditions).
Select(Function(o) New CustomerPositionFromPaidDto() With {.FundingYearId = o.FundingYearId.Value, .DsoId = o.DsoId.Value, .CustomerId = o.CustomerId.Value, .CustomerPosition = o.CustomerPosition.Value}).
ToList()
Return customerPositionsFromPaid
End Function
--Select All
Public Shared Function [SelectAll](ByVal conditions As Expression(Of Func(Of T, Boolean))) As IEnumerable(Of T)
Return [SelectAll]().Where(conditions)
End Function
Public Shared Function [SelectAll]() As IQueryable(Of T)
Return Table
End Function
Private Shared ReadOnly Property Table() As Table(Of T)
Get
Return Context.GetTable(Of T)()
End Get
End Property
I manage to solve the above by writing the following code. Looks like because customerid and fundingyearid are nullable objects, i had to use .Value attribute but still not sure why the previous code will work on one server and not on the other one.
Private Shared Function GetCustomerPositionsFromPaid(ByVal customerID As Integer, ByVal fundingYearID As Integer) As IEnumerable(Of CustomerPositionFromPaidDto)
Dim conditions = PredicateBuilder.True(Of vwCustomerPositionInPaid)()
conditions = conditions.And(Function(o) o.CustomerId.Equals(customerID.Value))
conditions = conditions.And(Function(o) o.FundingYearId.Equals(fundingYearID.Value))
conditions = conditions.And(Function(o) o.DsoId.HasValue)
Dim customerPositionsFromPaid = vwCustomerPositionInPaid.
SelectAll().
Where(conditions).
Select(Function(o) New CustomerPositionFromPaidDto() With {.FundingYearId = o.FundingYearId.Value, .DsoId = o.DsoId.Value, .CustomerId = o.CustomerId.Value, .CustomerPosition = o.CustomerPosition.Value}).
ToList()
Return customerPositionsFromPaid
End Function

Call a different string method based on a flag in VB.NET

I want to be able to call a different String method based on what is passed in a parameter. For example, my parameter is "%XYZ%". If there are percent signs on both sides of XYZ, further down in code I want to say SomeString.Contains("XYZ").
If there is a percent sign only on the left of XYZ, further down in code I want to say
SomeString.EndsWith("XYZ").
Ideally, I want something like this:
With objSearchTerms
If Not String.IsNullOrEmpty(.ProjectName) Then
If .ProjectName.StartsWith("%") AndAlso .ProjectName.EndsWith("%") Then
'MyStringMethod = Contains
ElseIf .ProjectName.EndsWith("%") Then
'MyStringMethod = StartsWith
ElseIf .ProjectName.StartsWith("%") Then
'MyStringMethod = EndsWith
Else
'MyStringMethod = Equals
End If
End If
End With
Then further down I want to be able to say:
filingList = filingRepository.GetList (Function(e) e.SERFFTrackingToFilings.Any(Function(x) x.SERFFTracking.Number.*MyStringMethod*(objTerms.TrackingNumber))
Thank you.
If you had a reference to the string that you want to run the method on, then you could assign the method to a Func<T, TResult> delegate, and invoke the delegate, something like.
Dim MyStringMethod As Func(Of String, Boolean)
'...
MyStringMethod = AddressOf(SomeString.Contains)
'...
Dim result As Boolean = MyStringMethod(SomeOtherString) 'SomeString.Contains(SomeOtherString)
But it looks like your SomeString is in an anonymous function where you may not already have a reference to it. Possible workaround:
Dim MyStringMethod As Func(Of String, String, Boolean)
'...if...
MyStringMethod = Function(a As String, b As String) a.Contains(b)
'...else if...
MyStringMethod = Function(a As String, b As String) a.StartsWith(b)
'...etc.
filingList = filingRepository.GetList (Function(e) e.SERFFTrackingToFilings.Any(Function(x) MyStringMethod(x.SERFFTracking.Number, objTerms.TrackingNumber)))
I ended up doing the following:
I created a Module:
Module StringExtensions
<Extension()>
Public Function ProcessString(ByVal strToCheck As String, ByVal strOperand As String, ByVal strWhatTocheck As String) As Boolean
Select Case strOperand
Case "Contains"
Return strToCheck.Trim.ToUpper.Contains(strWhatTocheck.Trim.ToUpper)
Case "StartsWith"
Return strToCheck.Trim.ToUpper.StartsWith(strWhatTocheck.Trim.ToUpper)
Case "EndsWith"
Return strToCheck.Trim.ToUpper.Contains(strWhatTocheck.Trim.ToUpper)
Case "Equals"
Return strToCheck.Trim.ToUpper.Equals(strWhatTocheck.Trim.ToUpper)
Case Else
Throw New ApplicationException("Can't match ProcessString(). ")
End Select
End Function
End Module
Then in the calling method I used the module like this:
Dim filingListDTO As New List(Of DTO.FilingDetailsViewModel)
Dim strNumberOperand As String = ""
Dim strProjectNumber As String = ""
With model
If Not String.IsNullOrEmpty(.ProjectNumber) Then
If .ProjectNumber.StartsWith("%") AndAlso .ProjectNumber.EndsWith("%") Then
strNumberOperand = "Contains"
ElseIf .ProjectNumber.EndsWith("%") Then
strNumberOperand = "StartsWith"
ElseIf .ProjectNumber.StartsWith("%") Then
strNumberOperand = "EndsWith"
Else
strNumberOperand = "Equals"
End If
strProjectNumber = .ProjectNumber.Replace("%", "").Trim.ToUpper
.ProjectNumber = strProjectNumber
End If
End With
filingListDTO = (From f In <<someList>>
Where f.ProjectNumber.ProcessString(strNumberOperand, strProjectNumber) _
Select f).ToList

List of IEnumerables

Please see the code below, which was written by someone else and works very well:
Public Function GetMembers(Optional ByVal sortExpression As String = "MemberId ASC") As List(Of Member) Implements IMemberDao.GetMembers
Dim sql As String =
" SELECT MemberId, Email, CompanyName, City, Country" &
" FROM [Member] ".OrderBy(sortExpression)
Return db.Read(sql, Make).ToList()
End Function
Public Iterator Function Read(Of T)(ByVal sql As String, ByVal make As Func(Of IDataReader, T), ParamArray ByVal parms() As Object) As IEnumerable(Of T)
Using connection = CreateConnection()
Using command = CreateCommand(sql, connection, parms)
Using reader = command.ExecuteReader()
Do While reader.Read()
Yield make(reader)
Loop
End Using
End Using
End Using
End Function
Private Shared Make As Func(Of IDataReader, Member) =
Function(reader) _
New Member() With {
.MemberId = Extensions.AsId(reader("MemberId")),
.Email = Extensions.AsString(reader("Email")),
.CompanyName = Extensions.AsString(reader("CompanyName")),
.City = Extensions.AsString(reader("City")),
.Country = Extensions.AsString(reader("Country"))
}
I understand that Make is a delegate that populates the Member objects with values, but I do not understand how a list of Persons is returned by the Read function? (a list is returned and works very well).
it does this by the yield keyword. This is generally how it works when you are iterating over a collection. Take a look here:
http://msdn.microsoft.com/en-us/library/vstudio/hh156729.aspx
This should give you a good/simple understanding.

Getting a "Type Error" in vb.net code converted from C#

I have this code in VB.Net 2010 and I am getting a "type Error" on New().
This code was converted from C#.
What am I doing wrong?
Public Function CredentialGet(ByVal sKey As String, ByRef sCred As String)
Dim sCredential As Element.Credential
sCredential = apiclient.SearchCredentials(sSoftwareKey, SessionID,
New() {New Element.SearchTerm() With {.FilterKey = "APK", .Value = sKey}})
sCred = sCredential.CredentialID
End Function
New() what? You are missing an object name there. You're now passing in an anonymous object.
Remove the parentheses for the anonymous type. VB.Net does not use them in that context, but looks for the With keyword instead. And functions should return a value. You don't return anything, so use a Sub:
Public Sub CredentialGet(ByVal Key As String, ByRef Cred As String)
Dim Credential As Element.Credential
Credential = apiclient.SearchCredentials(sSoftwareKey, SessionID, _
New With {New Element.SearchTerm() With {.FilterKey = "APK", .Value = Key}})
Cred = Credential.CredentialID
End Sub
I also question this design. It would be better to return a string:
Public Function CredentialGet(ByVal Key As String) As String
Return apiclient.SearchCredentials(sSoftwareKey, SessionID, _
New With {New Element.SearchTerm() With {.FilterKey = "APK", .Value = Key}}).CredentialID
End Function

Making lambda expression dynamically

In Entity Framework I usually do something like:
modelBuilder.Entity(Of Model).HasKey(Function(item As Model) New With {item.PropertyA, item.PropertyB })
to map a composite primary key
I need to write a generic function like:
modelBuilder.Entity(Of TModelo).HasKey( MakeLambda({“PropertyA”, “PropertyB” })
Private Function MakeLambda(Of TModelo)(nameProperties As String()) As Expression(Of Func(Of TModelo, Object))
Dim type = GetType(TModelo)
Dim listProperties As New List(Of Expression)
Dim parameter = Expression.Parameter(type, "item")
For Each n As String In nameProperties
Dim refProperty = type.GetProperty(n)
listProperties.Add(Expression.MakeMemberAccess(parameter, refProperty))
Next
Dim arrayInit = Expression.NewArrayInit(GetType(Object), listProperties)
In this point the system fails creating the new expression
Dim newExpression = Expression.Lambda(Of Func(Of TModelo, Object))(arrayInit)
Return newExpression
End Function
May be somebody has another solution to this problem
This will do.
dynamic newExpression = Expression.Lambda>(arrayInit, parameter);
But this still not work for me yet.
I need something like this...
HasKey(p => new { p.FAMILY, p.CACHE_FAMILY, p.CUSTOMER_CODE, p.CCC, p.OPERATION, p.EVAL_CODE, p.VDT_FLAG, p.TEST_PLATFORM, p.PCBA_VENDOR });