List contains duplicate Persons - vb.net

Please see the code below:
Public Function ExecuteDynamicQuery(ByVal strSQL As String, ByVal list As List(Of clsType), ByVal tyType As clsType) As List(Of clsType) Implements IGenie.ExecuteDynamicQuery
Dim objParameterValues As New clsParameterValues
Dim iConnectionBLL As iConnectionBLL
Dim objCon As DbConnection
Dim objDR As DbDataReader
Dim paramValues() As DbParameter
objParameterValues = New clsParameterValues
iConnectionBLL = New clsConnectionBLL()
objCon = iConnectionBLL.getDatabaseTypeByDescription("Genie2")
Using objCon
paramValues = objParameterValues.getParameterValues
objDR = clsDatabaseHelper.ExecuteReader(objCon, CommandType.Text, strSQL, paramValues)
Do While objDR.Read
Dim tyType2 As clsType = tyType
tyType.PopulateDataReader(objDR)
list.Add(tyType2)
Loop
objDR.Close()
Return list
End Using
End Function
An SQL statement is passed to the function along with clsType (the base type). A list of types is returned e.g. a list of Persons. For example, in this case strSQL = "SELECT * FROM Persons". A list of 500 persons is returned but they are all the same person (the last person added to the list). I realise this is because the list is referncing the same object for each entry. How do I change this?

This is a situation where making the method generic would be useful. For instance:
Public Function MyGenericMethod(Of T As New)() As List(Of T)
Dim results As New List(Of T)()
For i As Integer = 0 To 9
Dim item As New T()
' Populate item ...
results.Add(item)
Next
Return results
End Function
For what it's worth, though, I see people trying do this kind of thing often, and it never sits well with me. I'm always the first one in line to suggest that common code should be encapsulated and not duplicated all over the place, but, I've never been convinced that creating some sort of data access layer that encapsulates the calls to ADO, but doesn't also encapsulate the SQL, is a good idea.
Consider for a moment that ADO, is in-and-of-itself an encapsulation of that part of the data-access layer. Sure, it can take a few more lines of code than you might like to execute a simple SQL command, but that extra complexity is there for a reason. It's necessary in order to support all of the features of the data source. If you try to simplify it, inevitably, you will one day need to use some other feature of the data source, but it won't be supported by your simplified interface. In my opinion, each data access method should use all of the necessary ADO objects directly rather than trying to some how create some common methods to do that. Yes, that does mean that many of your data access methods will be very similar in structure, but I think you'll be happier in the long run.

I've reduced your original code. The following sample is functionally equivalent to what you posted. Without knowing more about your types, it will hard to give you anything more than this, but maybe the reduction will make the code clear enough for you to spot a solution:
Public Function ExecuteDynamicQuery(ByVal sql As String, ByVal list As List(Of clsType), ByVal type As clsType) As List(Of clsType) Implements IGenie.ExecuteDynamicQuery
Dim paramValues() As DbParameter = New clsParameterValues().getParameterValues()
Using conn As DbConnection = iConnectionBLL.getDatabaseTypeByDescription("Genie2"), _
rdr As DbDataReader = clsDatabaseHelper.ExecuteReader(conn, CommandType.Text, sql, paramValues)
While rdr.Read()
type.PopulateDataReader(rdr)
list.Add(type)
End While
Return list
End Using
End Function
There are a few additional bits of advice I can give you:
You must have some way to accept parameter information for your query that is separate from the query itself. The ExecuteReader() method that you call supports this, but you only ever pass it an empty array. Fix this, or you will get hacked.
A implementation that uses Generics (as posted in another answer) would be much simpler and cleaner. The Genie interface you're relying doesn't seem to be adding much value. You'll likely do better starting over with a system that understands generics.
The problem of re-using the same object over and over can be fixed by creating a new object inside the loop. As written, the only way to do that is with a New clsType (and it seems you may have Option Strict Off, such that this could blow up on you at run time), through some messy reflection code, a switch to using generics as suggested in #2, or a by accepting a Func(Of clsType) delegate that can build the new object for you.

Related

CopyToDataTable(Of DataRow) shows syntax error

Im trying to convert the output of a LINQ query to a datatable, I have the following code but it shows a syntax error in the (Of DataRow) part:
Dim X As New Entities
Dim query As IEnumerable(Of DataRow) = From cajero In X.CAJERO.AsEnumerable
Select cajero
Dim bla = query.CopyToDataTable(Of DataRow)()
I'm using this question as a guide:
Filling a DataSet or DataTable from a LINQ query result set
If i use
query.CopyToDataTable()
'instead of the overload
query.CopyToDataTable(Of DataRow)
it throws an invalidCastException.
I'm looking for an easy way to accomplish this task, and this seemed to be the easiest one, without too much code and having to implement any "shredder" methods or such, but if that's the only way, then please point me in the right direction.
This is the error that throws(I localized it to english, its a bit different in spanish):
Cannot convert object of type WhereSelectEnumerableIterator to object of type IEnumerable System.Data.DataRow
I have tried the following:
Declare an extension method that would create the datatable like this:
_
Public Function myToDataTable(Of T)(source As IEnumerable(Of T)) As DataTable
Dim properties As PropertyInfo() = GetType(T).GetProperties()
Dim output As New DataTable()
For Each prop In properties
output.Columns.Add(prop.Name, prop.PropertyType)
Next
For Each item In source
Dim row As DataRow = output.NewRow()
For Each prop In properties
row(prop.Name) = prop.GetValue(item, Nothing)
Next
output.Rows.Add(row)
Next
Return output
End Function
But it always throws an exception while adding the columns to the datatable:
DataSet does not support System.Nullable
I also changed the linq query to this:
Dim query = From cajero In X.CAJERO
Select cajero
Dim bla = query.myToDataTable
Following Jon's suggestion I found this question:
.NET - Convert Generic Collection to DataTable
Which just gave me the last bit of code I needed:
output.Columns.Add(prop.Name, If(Nullable.GetUnderlyingType(prop.PropertyType), prop.PropertyType))
and
row(prop.Name) = If(prop.GetValue(item, Nothing), DBNull.Value)
I believe the problem is that you're trying to convert an IEnumerable<T> of an arbitrary entity type - whereas CopyToDataTable() always requires the input to be a sequence of some kind of DataRow.
Unless your entity type actually derives from DataRow, that's not going to work. You could potentially write a LINQ query which creates a DataRow from each instance, but I believe you'll have to write that code yourself.
I believe in the question you referenced, the OP already had a strongly typed DataSet - at least the answer suggested that's the case.

VB : Is it possible to cast/convert from List(Of DataRow) to List(Of String)?

I'm trying to solve a problem regarding types of list. First of all I have a stored procedure in my DB which does a select of a single column and I try to proceed it in my app in VB. By making a method function I declared a DataTable that loads through the SqlCommand(with the CloseConnection behavior). After that I publicly declared a List(Of String) which needs to be populated with the rows/items from the stored procedure that is on the way. Below is my snippet of the code:
Dim dt As New DataTable()
Try
If conn.State = ConnectionState.Open Then
conn.Close()
Else
conn.Open()
Dim cmd = New SqlCommand("LoadCodes", conn)
cmd.CommandType = CommandType.StoredProcedure
dt.Load(cmd.ExecuteReader(CommandBehavior.CloseConnection))
Dim collection As New List(Of DataRow)
collection = dt.AsEnumerable.ToList
LPrefix = collection.Cast(Of String)()
End If
Catch ex As Exception
MsgBox(ex.Message + vbCritical)
End Try
It's LPrefix = collection.Cast(Of String)() where I get an exception error telling me that I can't really convert it. The old fashion way is to iterate with for/for each loop but that's not what I want for best use of performance especially if the list will have thousands of rows from a single column. So basically I want to insert those items from that DataTable to the List(Of String) without using For/For Each loop.
Running on Visual Studio 2010 Ultimate, .NET Framework 4.0.
You don't need your collection at all. Using LINQ, you can extract the first column directly out of your data table:
dt.Load(cmd.ExecuteReader(CommandBehavior.CloseConnection))
LPrefix = (From row In dt.AsEnumerable()
Select row.Field(Of String)(0)).ToList()
Of course, this might use a loop internally, but since you want to copy each value into a list of strings, you cannot do it without looping through the data rows.
Another alternative would be to use an IEnumerable(Of String) instead of a List(Of String):
dt.Load(cmd.ExecuteReader(CommandBehavior.CloseConnection))
Dim LPrefixNew As IEnumerable(Of String) = _
From row In dt.AsEnumerable()
Select row.Field(Of String)(0)
You can iterate through IEnumerable just as you would through a list, but evaluation is lazy: As long as you don't access the elements, the DataTable is not traversed. So, accessing this IEnumerable is like reading the elements directly from the DataTable, just in a more convenient way.
Another word of advice: You should not try to reason about performance until you have measured it. For example, your line collection = dt.AsEnumerable.ToList probably already loops through your entire DataTable and copies each DataRow reference into a List of DataRows; so, with this line, you already have the performance penalty that you are trying to avoid.
So, don't automatically assume that some For loop is always slower than some single statement. Measure it, then optimize.
Assuming your DataRow only has one column you just need to instruct ConvertAll to cast it:
LPrefix = collection.ConvertAll(Function(x) x[0].ToString)
Thanks to Binary Worrier for c#-2-vb translation!

Returning a DataTable in WCF and .NET

Thanks William, that was the ticket. Had to assign the name property on both ends [DataTable.TableName].
On a side note here: There appears to be some school of thought (no offense Marc) that the following statement is always true:
"Everything in the world can and should be made into an object."
It is, simply, not always true. There are cases where you cannot cram your 'object' into any cookie-cutter or class no matter how you try to customize it. For me to objectize this beast, I'd have to create roughly 4,000 objects. I don't have time to do that, and yet this project must run as a service. Frankly I think the developers at MickeySoft need to get out more into the real world, and see for themselves that although theory is great it does not represent true-life challenges. I am all for working with objects for the obvious benefits, but there is a reality that universals do not exist. In my trade, even 'most of the time' rarely happens.
Before they push out a new technology set, and cease support of an old one, they need to make sure that the new technology has the same capabilities the old one had.
For the record: The people who believe the above statement to be true are also the same people who would refuse to take the project I'm currently working on.
Just the same -- thank you both for your time, efforts and opinions!
I'm trying to create a WCF function that will return a table to my console testing app. I am a total noob. The data is 2-dimensional and looks like this:
23 Letter
42 Another Letter
43 Document
...
Here's what I'm trying to do:
<ServiceContract()> _
Public Interface ILetterWriter
<OperationContract()> _
Function GetLetter(ByVal LetterID As String, ByVal StateID As String, ByVal CompID As String, ByVal tblVar As DataTable) As String
<OperationContract()> _
Function GetLetterNames(ByVal DepartmentType As Integer) As DataTable
End Interface
Public Function GetLetterNames(ByVal DepartmentType As Integer) As DataTable Implements ILetterWriter.GetLetterNames
Dim SQLCon As New SqlClient.SqlConnection
Dim SQLCmd As New SqlClient.SqlCommand
'Connect to the database
SQLCon.ConnectionString = "Data Source=VMSQL08-SRV1;Initial Catalog=DotNetDev;User ID=aiis_pgmr;Password=ag58102;"
SQLCon.Open()
'Grab the stored procedure, which returns the letter names
SQLCmd.CommandText = "sp_GetLetters"
SQLCmd.CommandType = CommandType.StoredProcedure
SQLCmd.Connection = SQLCon
'Pass the parameters
SQLCmd.Parameters.AddWithValue("#LetterType", DepartmentType)
'Execute the stored procedure, fill the datatable from a data adapter
GetLetterNames = New DataTable
GetLetterNames.Load(SQLCmd.ExecuteReader)
'Shut it down
SQLCmd.Dispose()
SQLCon.Close()
SQLCon.Dispose()
End Function
...Of course, it won't work. I just need to get the WCF to pass a basic table to my console application. The execute SQL seems to work just fine, I just can't get the data back to my application.
Any help would be greatly appreciated.
Thanks,
Jason
I agree with the other poster.
However, if you are returning a DataTable, you have to set the "Name" property of the DataTable if you want to return it from a WCF Service.

Linq to Datarow, Select multiple columns as distinct?

basically i'm trying to reproduce the following mssql query as LINQ
SELECT DISTINCT [TABLENAME], [COLUMNNAME] FROM [DATATABLE]
the closest i've got is
Dim query = (From row As DataRow In ds.Tables("DATATABLE").Rows _
Select row("COLUMNNAME") ,row("TABLENAME").Distinct
when i do the above i get the error
Range variable name can be inferred
only from a simple or qualified name
with no arguments.
i was sort of expecting it to return a collection that i could then iterate through and perform actions for each entry.
maybe a datarow collection?
As a complete LINQ newb, i'm not sure what i'm missing.
i've tried variations on
Select new with { row("COLUMNNAME") ,row("TABLENAME")}
and get:
Anonymous type member name can be
inferred only from a simple or
qualified name with no arguments.
to get around this i've tried
Dim query = From r In ds.Tables("DATATABLE").AsEnumerable _
Select New String(1) {r("TABLENAME"), r("COLUMNNAME")} Distinct
however it doesn't seem to be doing the distinct thing properly.
Also, does anyone know of any good books/resources to get fluent?
You start using LINQ on your datatable objects, you run the query against dt.AsEnumberable, which returns an IEnumerable collection of DataRow objects.
Dim query = From row As DataRow In ds.Tables("DATATABLE").AsEnumerable _
Select row("COLUMNNAME") ,row("TABLENAME")
You might want to say row("COLUMNNAME").ToString(), etc. Query will end up being an IEnumerable of an anonymous type with 2 string properties; is that what you're after? You might need to specify the names of the properties; I don't think the compiler will infer them.
Dim query = From row As DataRow In ds.Tables("DATATABLE").AsEnumerable _
Select .ColumnName = row("COLUMNNAME"), .TableName = row("TABLENAME")
This assumes that in your original sql query, for which you used ADO to get this dataset, you made sure your results were distinct.
Common cause of confusion:
One key is that Linq-to-SQL and (the Linq-to-object activity commonly called) LINQ-to-Dataset are two very different things. In both you'll see LINQ being used, so it often causes confusion.
LINQ-to-Dataset
is:
1 getting your datatable the same old way you always have, with data adapters and connections etc., ending up with the traditional datatable object. And then instead of iterating through the rows as you did before, you're:
2 running linq queries against dt.AsEnumerable, which is an IEnumerable of datarow objects.
Linq-to-dataset is choosing to (A) NOT use Linq-to-SQL but instead use traditional ADO.NET, but then (B) once you have your datatable, using LINQ(-to-object) to retrieve/arrange/filter the data in your datatables, rather than how we've been doing it for 6 years. I do this a lot. I love my regular ado sql (with the tools I've developed), but LINQ is great
LINQ-to-SQL
is a different beast, with vastly different things happening under the hood. In LINQ-To-SQL, you:
1 define a schema that matches your db, using the tools in in Visual Studio, which gives you simple entity objects matching your schema.
2 You write linq queries using the db Context, and get these entities returned as results.
Under the hood, at runtime .NET translates these LINQ queries to SQL and sends them to the DB, and then translates the data return to your entity objects that you defined in your schema.
Other resources:
Well, that's quite a truncated summary. To further understand these two very separate things, check out:
LINQ-to-SQL
LINQ-to-Dataset
A fantastic book on LINQ is LINQ in Action, my Fabrice Marguerie, Steve Eichert and Jim Wooley (Manning). Go get it! Just what you're after. Very good. LINQ is not a flash in the pan, and worth getting a book about. In .NET there's way to much to learn, but time spent mastering LINQ is time well spent.
I think i've figured it out.
Thanks for your help.
Maybe there's an easier way though?
What i've done is
Dim comp As StringArrayComparer = New StringArrayComparer
Dim query = (From r In ds.Tables("DATATABLE").AsEnumerable _
Select New String(1) {r("TABLENAME"), r("COLUMNNAME")}).Distinct(comp)
this returns a new string array (2 elements) running a custom comparer
Public Class StringArrayComparer
Implements IEqualityComparer(Of String())
Public Shadows Function Equals(ByVal x() As String, ByVal y() As String) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of String()).Equals
Dim retVal As Boolean = True
For i As Integer = 0 To x.Length - 1
If x(i) = y(i) And retVal Then
retVal = True
Else
retVal = False
End If
Next
Return retVal
End Function
Public Shadows Function GetHashCode(ByVal obj() As String) As Integer Implements System.Collections.Generic.IEqualityComparer(Of String()).GetHashCode
End Function
End Class
Check out the linq to sql samples:
http://msdn.microsoft.com/en-us/vbasic/bb688085.aspx
Pretty useful to learn SQL. And if you want to practice then use LinqPad
HTH
I had the same question and from various bits I'm learning about LINQ and IEnumerables, the following worked for me:
Dim query = (From row As DataRow In ds.Tables("DATATABLE").Rows _
Select row!COLUMNNAME, row!TABLENAME).Distinct
Strangely using the old VB bang (!) syntax got rid of the "Range variable name..." error BUT the key difference is using the .Distinct method on the query result (IEnumerable) object rather than trying to use the Distinct keyword within the query.
This LINQ query then returns an IEnumerable collection of anonymous type with properties matching the selected columns from the DataRow, so the following code is then accessible:
For Each result In query
Msgbox(result.TABLENAME & "." & result.COLUMNNAME)
Next
Hoping this helps somebody else stumbling across this question...

How do I update a single table of a DataSet using a TableAdapter, without hard-coding the table name?

This seems like a really basic thing that I'm doing, yet I'm tearing my hair out trying to make it work.
My situation is this: I have a project which contains a large number of lookup tables, and I have all of these lookup tables represented in a single typed DataSet, which contains TableAdapters for each lookup. I've designed an editor for these lookup tables, which should allow editing of one of these at a time. My front-end is written in VB and WinForms, the back-end is a SOAP web service; I can successfully pass the changes to the DataSet back to the web service, but can't find a way to use a TableAdapter to update the single table that has been changed.
What I'm trying to do is instantiate the appropriate TableAdapter for the updated DataTable by sending the name of the table back to the web service along with the DataSet, then referring to the TableAdapter with a dynamic name. The normal way to instantiate a TableAdapter is this:
Dim ta As New dsLookupsTableAdapters.tlkpMyTableTableAdapter
What I'd like to do is this, but of course it doesn't work:
strTableName = "tlkpMyTable"
Dim ta As New dsLookupsTableAdapters(strTableName & "TableAdapter")
Is there any way to achieve this, or am I taking the wrong approach altogether? My other alternative is to write separate code for each table, which I'd prefer to avoid!
You can use Activator to create an instance of your TableAdapter from its string name, just like you want:
object adapter = Activator.CreateInstance(Type.GetType("My.Namespace.MyDataSetTableAdapters." + myTable.Name + "TableAdapter"));
Then, because TableAdapters don't have a common interface, you should use reflection to call its Update method:
adapter.GetType().GetMethod("Update").Invoke(adapter, null);
http://msdn.microsoft.com/en-us/library/system.type.getmethod.aspx
This is from memory, but roughly close enough. You can also use GetProperty to get the connection property and set it as required.
Not sure I 100% understand, do you have a single DataTable in your DataSet, or one DataTable per lookup table?
Anyway, perhaps you could you this approach to filter by lookup table?
It's pretty easy to create types at runtime given the (string) type name.
Here's a self-contained VB class which illustrates one way to do it: use System.Activator.CreateInstance to create instances of types using a string representation of the type name. Then you can cast it to a DataAdapter base class and use it like any other DataAdapter.
Public Class dsLookupsTableAdapters
Public Function CreateInstance(ByVal strName As String) As Object
CreateInstance = Nothing
For Each a As System.Reflection.Assembly In System.AppDomain.CurrentDomain.GetAssemblies()
Try
Dim strAssemblyName As String() = a.FullName.Split(New Char() {","c})
Dim strNameTemp As String = strAssemblyName(0) & "." & strName
Dim instance As Object = System.Activator.CreateInstance(a.FullName, strNameTemp)
If instance IsNot Nothing Then
Dim handle As System.Runtime.Remoting.ObjectHandle
handle = CType(instance, System.Runtime.Remoting.ObjectHandle)
Dim o As Object = handle.Unwrap()
CreateInstance = o
Exit For
End If
Catch ex As System.Exception
Continue For ' ignore exception, means type isn't there
End Try
Next
End Function
Public Class tlkpMyTableTableAdapter
Inherits System.Data.Common.DataAdapter
End Class
Public Sub Test()
' define type name. note that, in this sample, tlkpMyTableTableAdapter is a nested
' class and dsLookupsTableAdapters is the containing class, hence the "+". If, however,
' dsLookupsTableAdapters is a namespace, replace the "+" with a "."
Dim typeName As String = "dsLookupsTableAdapters+tlkpMyTableTableAdapter"
Dim adapter As System.Data.Common.DataAdapter
Dim o As Object = CreateInstance(typeName)
adapter = CType(o, System.Data.Common.DataAdapter)
End Sub
End Class
If you are using VB.Net 2008, then use the tableadaptermanager (http://msdn.microsoft.com/en-us/library/bb384426.aspx). I think this would be much easier to code against :)
Wade