LINQ. Reducing the code by using dynamic queries - vb.net

I use the following code to fill the Table1 dictionary with the information found within the LINQ query.
Dim DB As New DatabaseDataContext
Dim Table1 As New Dictionary(Of String, Integer)
Dim Table2 As New Dictionary(Of String, Integer)
Private Function FillTable() As Dictionary(Of String, Integer)
Table1.Clear()
Dim Query = From c In DB.Table1 Select New With _
{.Table1ID = c.Table1ID, .Table1 = c.Table1}
For Each c In Query
Table1.Add(c.Table1, c.Table1ID)
Next
Return Table1
End Function
What changes should i make to the function above to fill any given TableXXX dictionary?
You see, I would not like to use the function below to fill the Table2 dictionary.
Private Function FillTable2() As Dictionary(Of String, Integer)
Table2.Clear()
Dim Query = From c In DB.Table2 Select New With _
{.Table2ID = c.Table2ID, .Table2 = c.Table2}
For Each c In Query
Table2.Add(c.Table2, c.Table2ID)
Next
Return Table2
End Function

What about the "ToDictionary()" extension method?
http://msdn.microsoft.com/en-us/library/bb549277.aspx

I don't know if this is true but this seems the VB version of the MS c# SimpleLinqToDatabase sample app.
If that is so it would work if you make all your Table row data model types have the same properties TableID and table. Then getting the table data with the generic method. If you don't want that you will need to alter the base model to have a property accessor by string with reflection, but that is not very clever and fast to do on the datamodel.
Private Function FillTable(Of T)() As Dictionary(Of String, Integer)
Dim dict as New Dictionary(Of String, Integer)
Dim Query = From c In DB.GetTable(Of T) Select New With _
{.TableID = c.TableID, .Table = c.Table}
For Each c In Query
dict.Add(c.Table, c.TableID)
Next
Return dict
End Function
Then call this with:
Dim result as Dictionary(Of String, Integer)
result = FillTable(Of Table1)()

Related

Implementing Custom GroupBy function in VB.Net

I'm trying to implement a GroupBy function in such a way that it's generic and we don't end up having to keep adding properties every time the client wants something added/changed (yes, I know there's other options. Limited time/budget restrict the scope to this or hard-coding more properties, so I'm trying to opt for the better solution).
Currently I have the following;
<!-- language: lang-vb -->
Dim groupByColumns = New Dictionary(Of String, Boolean) From {
{"Col1", True},
{"Col2", False},
...
}
Dim groupedData = data.GroupBy(Function(r)
Dim groupingResult = New List(Of KeyValuePair(Of String, Object))
For Each column In groupByColumns
Dim groupingValue As Object = Nothing
If column.Value Then
groupingValue = TypeDescriptor.GetProperties(r).Item(grouping.Key).GetValue(r)
End If
groupingResult.Add(New KeyValuePair(Of String, Object)(column.Key, groupingValue))
Next
Return groupingResult
End Function)
If I then do an OrderBy or Select it seems to be returning an IGrouping, which is what I would expect, only the two data rows in the data object are still ungrouped, even though they are identical. I'm out of google-fu, can anybody please assist?
You could combine the fields to group by into one key (probably a string) and then group by that value:
<!-- language: lang-vb -->
Dim groupedData2 = data.GroupBy(Function(r)
Dim groupKey As New List(Of String)
For Each column In groupByColumns
If column.Value Then
Dim p = r.GetType().GetProperties().FirstOrDefault(Function(p) p.Name = column.Key)
Dim groupingValue = p?.GetValue(r)
groupKey.Add(groupingValue)
End If
Next
Return String.Join(",", groupKey)
End Function)

How to make a generic database record to object conversion function?

I am using the following function to retrieve records from a database and convert the records to a collection of strongly typed objects.
Private Function GetPlantSettingsFiltered(parameters As Dictionary(Of String, Object), queryCondition As String) As PlantSettings
Dim query As String
query = " SELECT * FROM Plant_Settings " _
+ queryCondition
Dim settings As New PlantSettings
Dim table As DataTable = GetQueryResults(parameters, query, GetConnectionString("WeighScaleDB"))
If table Is Nothing Then
Return settings
End If
For Each row As DataRow In table.Rows
settings.Add(New PlantSetting With {
.Setting_ID = ConvertByteArrayToString(TryCast(row("Setting_ID"), Byte())),
.Plant_ID = ConvertByteArrayToString(TryCast(row("Plant_ID"), Byte())),
.Value = row("Setting_Value").ToString(),
.Comments = row("Setting_Comments").ToString()
})
Next
Return settings
End Function
I would like to create a generic version of this function that would work for any of my objects without me creating this function for each object.
For example, if the caller could specify the type, then some other details, the function would return a collection of that type.
Private Function GetObjects(Of T)(parameters As Dictionary(Of String, Object), query As String) As WSAEntityCollection(Of T)
Dim objectCollection As New WSAEntityCollection(Of T)
Dim table As DataTable = GetQueryResults(parameters, query, GetConnectionString("WeighScaleDB"))
If table Is Nothing Then
Return objectCollection
End If
For Each row As DataRow In table.Rows
' Here is my problem
objectCollection.Add(New T With {})
Next
Return objectCollection
End Function
My current problem with this new function is that I do not know how to dynamically match the column names with the parameters of the generic object. Any ideas on how this could be done?

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

Compare two lists 2D and determine differences VB.NET

I declare my 2D lists:
Dim _invoiceitems As New List(Of List(Of String))
Dim _dbitems As New List(Of List(Of String))
Each List is filled like this:
Example Code To fill:
_invoiceitems.Add(New List(Of String))
_invoiceitems(0).Add("Code #")
_invoiceitems(0).Add("Quantity")
Well, now i need a third list called (_changesitems) Note that this result with the differences:
be the result of subtraction of quantities if this is found (dbitems - invoiceitems).
How i can get this result?
The following code will generate the results you are looking for:
Private Function getChangesItems(ByVal invoiceItems As Dictionary(Of String, Integer), ByVal dbItems As Dictionary(Of String, Integer)) As Dictionary(Of String, Integer)
Dim changesItems As Dictionary(Of String, Integer) = New Dictionary(Of String, Integer)()
Dim allCodes As List(Of String) = New List(Of String)()
allCodes.AddRange(invoiceItems.Keys)
allCodes.AddRange(dbItems.Keys)
For Each code As String In allCodes
If Not changesItems.ContainsKey(code) Then
Dim dbQuantity As Integer = 0
Dim invoiceQuantity As Integer = 0
If dbItems.ContainsKey(code) Then
dbQuantity = dbItems(code)
End If
If invoiceItems.ContainsKey(code) Then
invoiceQuantity = invoiceItems(code)
End If
Dim changeQuantity As Integer = dbQuantity - invoiceQuantity
If changeQuantity <> 0 Then
changesItems.Add(code, changeQuantity)
End If
End If
Next
Return changesItems
End Function
I used dictionaries instead of lists as was recommended by others. As long as your data only contains a code and a value, the dictionary is a better fit. If you have more columns, I would suggest creating a class that contains properties for each column and then make a list of that class type, rather than a simple 2D list of strings. Doing so would be more type-safe and easier to read.

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.