List of IEnumerables - vb.net

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.

Related

Custom function(s) in LINQ to Entities? How to write acceptable code?

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

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

How to query an arraylist using Linq that stores dictionaries

The following code reads query results from oracle data reader and stores each record in a dictionary and appends the dictionaries to an array list :
Dim dr As OracleDataReader = cmd.ExecuteReader()
'loop oracle data records and store them to dictionaries
'append dictionaries to an array list
Dim arr As New ArrayList
While dr.Read
Dim dict As New Dictionary(Of String, Object)
For count As Integer = 0 To (dr.FieldCount - 1)
dict.Add(dr.GetName(count), dr(count))
Next
arr.Add(dict)
End While
How do I write a LINQ query that can be used to retrieve values from the dictionaries stored in the array list? Please help. I've been searching and have not got any good answers
First of all, don't use ArrayList, ever. It is there for backwards compatibility but has no usage. I can make answer short - there is no use of LINQ with ArrayList. Use generic List(Of T) and LINQ to search values in it. No need for Dictionary either. This is the old style. We used Dictionary because it has key
I see, you trying to create your table structure but no need for this. First of all, there is System.Data.DataTable, which can be queried on client.
Or use this technique
Public Class User
Public Property Id As Integer
Public Property Name As String
Public Property Email As String
Public Property Country As String
End Class
Private Function LoadUsers() As List(Of User)
Dim uList As New List(Of User)()
' Some Code goes here
While dr.Read()
Dim u As New User()
u.Id = dr("Id")
u.Name = dr("Name")
u.Email = dr("Email")
u.Country = dr("Country")
uList.Add(u)
End While
. . . . . . .
Return uList
End While
' somewhere in class set member variable
_users = LoadUsers()
' And then you can search for info using LINQ
Public Function FindByCountry(ByVal country As String) As List(Of User)
Return _users.Where(Function(u) u.Country.Equals(country, StringComparison.OrdinalIgnoreCase))
End
The downside of this approach - you need Find function for each field. But what if you can pass a function itself. See- you have Name, email, Country - all strings. Here what you can do
Class Client
Sub SearchStrings(ByVal searchOption String, Byval searchValue As String)
Dim f As Func(Of User, boolean)
If searchOption = "Name" Then
f = Function(u as User)(u.Name.Equals(searchValue , Stringcomparison.OrdinalIgnoreCase))
ElseIf searchOption = "Country" Then
f = Function(u as User)(u.Country.Equals(searchValue , Stringcomparison.OrdinalIgnoreCase))
ElseIf searchOption = "Email" Then
f = Function(u as User)(u.Email.Equals(searchValue , Stringcomparison.OrdinalIgnoreCase))
Else
. . . .
End If
dataGrd.DataSource = myRepository.FindByString(f)
End Sub
End Class
' In your repository class
public sub FindByString(ByVal f as Func(Of String, Boolean)) As List(Of User)
_users.Where(f).ToList()
End sub
' use this to search single user
public sub FindByInteger(ByVal f as Func(Of Integer, Boolean)) As User
_users.SingleOrDefault(f)
End sub
The bottom line - drop what you do and use modern and efficient techniques. And above are just couple of them

Yield with delegate

Please see the code below:
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) --line 7
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"))
}
Please see line 7. Make populates an object of type Member with values from the data reader row. I have read the following documentation: http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx. The documentation does not seem to explain what happens when you use a delegate i.e. Yield make(datareader), rather than Yield return datareader. Is control passed back to the calling function as well as the delegate (Make)?
Make(reader) is a shortcut for Make.Invoke(reader). I.e., you
invoke the delegate, passing reader as a parameter, which yields a value of type Member.
Then, you return that value using Yield.
It is equivalent to :
...
Do While reader.Read()
Dim myMember As Member = make(reader)
Yield myMember
Loop
...
PS: If you get a compile-time error in your code (you don't say so in your question): This is due to the fact that your method is declared to return an IEnumerable(Of T), when in fact it returns an IEnumerable(Of Member).

VB.Net I'm trying to write an extension for a generic linq search, however I'm not sure how to return more than one result 0.o

I'm a bit new to vb.net and used to working in perl, this is what I'd like to do.
I wanted something similar to DBIX::Class::Resultset's search (from cpan) in my vb.net project, so that I can give my function a hash containing keys and values to search on a table.
Currently it returns a single matching result of type T where I want it to return all results as a data.linq.table(of T)
How should I alter my expression.lambda so that I can say table.Select(Predicate) to get a set of results? After that I think it should be as simple as saying results.intersect(result) instead of Return test.
Any help will be very much appreciated.
Thanks in advance
-Paul
<System.Runtime.CompilerServices.Extension()> _
Public Function Search(Of T As Class)(ByVal context As DataContext, _
ByVal parameters As Hashtable) As T
Dim table = context.GetTable(Of T)()
Dim results As Data.Linq.Table(Of T)
For Each Parameter As DictionaryEntry In parameters
Dim column As Object = Parameter.Key
Dim value As String = Parameter.Value
Dim param = Expression.Parameter(GetType(T), column)
Dim Predicate = Expression.Lambda(Of Func(Of T, Boolean)) _
(Expression.[Call](Expression.Convert(Expression.Property(param, column), _
GetType(String)), GetType(String).GetMethod("Contains"), _
Expression.Constant(value)), New ParameterExpression() {param})
Dim test = table.First(Predicate)
Return test
' result.intersect(result)
Next
'Return results
End Function
This works assuming you want an "AND" conjunction between predicates
For instance:
Dim h = New System.Collections.Hashtable
h.Add("FieldA", "01 5149")
h.Add("FieldB", "WESTERN")
Dim t = (New DBDataContext).Search(Of DBrecord)(h)
Debug.Print(t.Count.ToString)
Would return those records where fieldA matched AND fieldb matched.
If you wanted OR, DiceGuy's right, use UNION.
Here's the search...
Note, I used STARTSWITH instead of contains because it's alot faster for large sets
You can always change it back.
<System.Runtime.CompilerServices.Extension()> _
Public Function Search(Of T As Class)(ByVal context As DataContext, _
ByVal parameters As Hashtable) As IQueryable(Of T)
Dim table = context.GetTable(Of T)()
Dim results As IQueryable(Of T) = Nothing
For Each Parameter As DictionaryEntry In parameters
Dim column = DirectCast(Parameter.Key, String)
Dim value As String = DirectCast(Parameter.Value, String)
Dim param = Expression.Parameter(GetType(T), column)
Dim Predicate = Expression.Lambda(Of Func(Of T, Boolean)) _
(Expression.[Call](Expression.Convert(Expression.Property(param, column), _
GetType(String)), GetType(String).GetMethod("StartsWith", New Type() {GetType(String)}), _
Expression.Constant(value)), New ParameterExpression() {param})
Dim r = table.Where(Predicate)
If results Is Nothing Then
results = r
Else
results = results.Intersect(r)
End If
Next
Return results
End Function
Well, for starters let's change the return type to Data.Linq.Table(Of T).
Then instead of table.First(Predicate), try table.Where(Predicate)
Finally 'Intersect' will only give you results that contain all your parameters. If that's what you want, then fantastic! If not, then try 'Union' instead.
Let me know where that gets you and we can work from there.