Data gets overwritten when adding class object to generic list - asp.net

I'm trying to add a class object (I think that's what it's called) to a generic list of my class. In the following code, whenever I change the values in TempQuoteReturnFromExecuteEstimate, it changes those values in all the rows of the generic list. I guess it's just referencing the object. How can I copy the values to the generic list? Thanks
Dim AllEstimatesReturn As New List(Of QuoteReturnData)
Dim TempQuoteReturnFromExecuteEstimate As New QuoteReturnData
...
AllEstimatesReturn.Add(TempQuoteReturnFromExecuteEstimate)

Your assumption is correct. When you create a class-type (reference-type) variable, it merely references an object. You can have many different variables all referencing the same object at the same time.
In this case, each item in your AllEstimatesReturn list is like a separate QuoteReturnData variable. Each item is merely a reference to an object, not a copy. So, theoretically, every item in a list can all reference the same object, although, usually that's not what you want.
If you want to make separate objects for each item in the list, you will need to create them, each time, using the New keyword:
'Add the first item
Dim TempQuoteReturnFromExecuteEstimate As New QuoteReturnData
...
AllEstimatesReturn.Add(TempQuoteReturnFromExecuteEstimate)
'Add the second item
TempQuoteReturnFromExecuteEstimate = New QuoteReturnData ' Use New to create another object
...
AllEstimatesReturn.Add(TempQuoteReturnFromExecuteEstimate)
Note that in the above example, I am reusing the same variable and just changing it's value so that it is referencing a new object. The old object is still there because it is referenced by the list object. Alternatively, you could declare a new variable each time, but, unless you want to do so for clarity, there is no technical reason why you need to do so.

Class types are reference types. Therefore this adds 3 references to the same object into the list:
Dim AllEstimatesReturn As New List(Of QuoteReturnData)
Dim TempQuoteReturnFromExecuteEstimate As New QuoteReturnData
AllEstimatesReturn.Add(TempQuoteReturnFromExecuteEstimate)
AllEstimatesReturn.Add(TempQuoteReturnFromExecuteEstimate)
AllEstimatesReturn.Add(TempQuoteReturnFromExecuteEstimate)
Either create new objects every time
Dim AllEstimatesReturn As New List(Of QuoteReturnData)
AllEstimatesReturn.Add(New QuoteReturnData())
AllEstimatesReturn.Add(New QuoteReturnData())
AllEstimatesReturn.Add(New QuoteReturnData())
Or implement a method that clones QuoteReturnData:
Class QuoteReturnData
' Creates an exact copy of the current QuoteReturnData object.
Public Function SwallowCopy() As QuoteReturnData
Return DirectCast(Me.MemberwiseClone(), QuoteReturnData)
End Function
End Class
MemberwiseClone is inherited from Object and does exactly what we need; however, this method is protected and can only be called from within the class. Therefore we wrap it into a public function.
Now you can do this
Dim AllEstimatesReturn As New List(Of QuoteReturnData)
Dim TempQuoteReturnFromExecuteEstimate As New QuoteReturnData
AllEstimatesReturn.Add(TempQuoteReturnFromExecuteEstimate.SwallowCopy())
AllEstimatesReturn.Add(TempQuoteReturnFromExecuteEstimate.SwallowCopy())
AllEstimatesReturn.Add(TempQuoteReturnFromExecuteEstimate.SwallowCopy())
Note that this only creates a shallow clone. If the object contains references to other objects, these other objects won't be cloned automatically. It's up to you to decide whether this is okay or whether you need a deep clone.

Objects are stored in references. Only value types are copied (i.e. structs).
In order to have a copy of your object, you need to clone it. Unless you do this, you'll be always creating many references to the same object. Your list isn't an exception.
I'd start implementing the ICloneable interface present in the .NET Framework.
Update
If you don't like the ICloneable interface because Clone() returns the object typed as object, you can implement an interface like this:
Public Interface ICloneable(Of T)
{
Function Clone() As T;
}
Your code adding items to the list will look something like this (if QuoteReturnData implements ICloneable(Of T), of course):
AllEstimatesReturn.Add(TempQuoteReturnFromExecuteEstimate.Clone());

Related

Inserting class property in list through foreach loop in vb.net

I am trying to insert class property in list through foreach loop in VB.NET.
Dim objCoInsurers As List(Of CoInsurer)
CoInsurer class consist of 5 properties
Below there is the code where class properties are inserted in my list
objCoInsurers = New List(Of CoInsurer)
Dim oCoInsurer As New CoInsurer
For Each row As GridViewRow In grdCoInsurance.Rows
Dim lblCoInsurerID = DirectCast(row.FindControl("lblCoInsurerID"), Label)
Dim textCoInsurerID As String = lblCoInsurerID.Text
Dim lblCoInsurerName = DirectCast(row.FindControl("lblCoInsurerName"), Label)
Dim CoInsurerName As String = lblCoInsurerName.Text
oCoInsurer.InsurerID = lblCoInsurerID.Text
oCoInsurer.InsurerName = lblCoInsurerName.Text
objCoInsurers.Add(oCoInsurer)
Next
The issue is when two list containing same value, the first list overwrite the second one, if row count is 2 in gridview or more
Consider this. Let's say that you want to add people to a list using pen and paper. I present myself to you wearing a blue shirt and you write down my name. I then take off my blue shirt and put on a red shirt and present myself to you and you write down my name. I then take off my red shirt and put on a green shirt and present myself to you and you write down my name. How many names do you have on the list? How many people do those names correspond to? What colour shirt do the people those names correspond to have on?
Given that OOP is based on the behaviour of real-world objects, think about what your code actually does. How many ColInsurer objects does your code create? Only one, right? It doesn't matter how many different colour shirts you put on that object and how many times you add it to the list, there's still only one object so it can only be wearing one shirt at a time. At the end of your loop, every item in the list refers to the same object and that object will have whatever property values you set last.
If you want to add multiple distinct objects to the list then you actually have to create multiple distinct objects. That means moving the creation of the object inside the loop, so it happens every iteration. You create a new object, set its properties and then add it to the list:
For Each row As GridViewRow In grdCoInsurance.Rows
Dim oCoInsurer As New CoInsurer
I am guessing your class looks something like this.
Public Class CoInsurer
Public Property InsurerID As String
Public Property InsurerName As String
End Class
I am wondering if InsurerID is a String and not an Integer or Long. If it is a number, be sure to surround the assignment value in the loop with CInt() or CLng()
I got rid of some of the unneeded variables. As jmcilhinney explained, you need to create a new instance of CoInsurer for every iteration of your loop.
Private Sub OPCode()
For Each row As GridViewRow In grdCoInsurance.Rows
Dim oCoInsurer As New CoInsurer
oCoInsurer.InsurerID = DirectCast(row.FindControl("lblCoInsurerID"), Label).Text
oCoInsurer.InsurerName = DirectCast(row.FindControl("lblCoInsurerName"), Label).Text
objCoInsurers.Add(oCoInsurer)
Next
End Sub

DataContext Disposal error resolved but why?

I am working with VB.Net. I have resolved a behavior where the code returns with an error of "Cannot access a disposed object. Object name: 'DataContext accessed after Dispose.'."
My problem : I simply don't understand why it works !
Here is my code when happens the error :
Public Function Element_Import(ByVal id_element As Integer) As elements
Using db As New GlobalDataContext(MyConnexion)
Return (From element In db.elements
Select element
Where element.id_element = id_element).First
End Using
End Function
Then I try to retreave the data :
Dim myElement As elements = _MyConnection.Element_Import(id_element)
Dim myLocal As List(Of elements_localisation) = myElement.elements_localisation.ToList '-- ObjectDisposedException !
When I import my element then try to access sub-tables called "elements_localisation" and "elements_files" an ObjectDisposedException occures. That's fair as the DataContext is not available.
So I have done something different :
Public Function Element_ImportContext(ByVal id_element As Integer) As elements
Dim myElement As elements
Dim myElementLocalisation As New List(Of elements_localisation)
Dim myElementFiles As New List(Of elements_files)
Using db As New GlobalDataContext(MyConnexion)
myElement = (From element In db.elements
Select element
Where element.id_element = id_element).First
myElementLocalisation = myElement.elements_localisation.ToList
myElementFiles = myElement.elements_files.ToList
End Using
Return myElement
End Function
Then I try to retreave the data :
Dim myElement As elements = _MyConnection.Element_ImportContext(id_element)
Dim myLocal As List(Of elements_localisation) = myElement.elements_localisation.ToList '-- IT WORKS !
I really wanna understand what happened as, in both case, the DataContext is disposed.
Can anyone explain ?
When you write LINQ, you write a query to be executed against some data, that query is only executed when another part of the code needs the data.
In your case, you are returning the myElement variable because you have used .First(), which forces the query to be executed and the first item to be returned.
The properties elements_localisation and elements_files are likely to be virtual properties that are only loaded when you request them. (This is certainly the case with Entity Framework, I'm not sure what you're using here).
Your program will try to access the virtual property which will then go off to the data context to get the next bit of data you requested, but in your case the data context is disposed.
Your second approach works because you have used ToList() on the virtual properties, which forces the data to be retrieved then and there, before your data context has been disposed.

Enable datagrid dynamic data at runtime

I have a data grid using dynamic data. I 'enable' dynamic data on the page_init event for the page containing the data grid. I would like to be able to set the type of the dynamic data at run time. I have the name of the class to set, as a string. I can't quite figure out how to do this.
I set the dynamic data like this:
Dim myGrid As GridView = DirectCast(retrieveGrid.FindControl("gridResults"), GridView)
myGrid.EnableDynamicData(GetType(*MyEntityNameAsAString*)
Obviously this does not work because I cannot provide my entity name a s a string. How can I convert the string to the entity type? I tried:
Type.GetType(entityname)
And
Type.GetType(AssemblyName.entityname)
And neither seems to work. That is, I can't get the type with either of these statements.
OK, solved it like this... I created a function to get the entity object from the object name:
Public Function GetEntity(ByVal entityName As String) As Object
'Get the assembly
Dim assem As Assembly = Nothing
assem = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory & "/bin/AsbestosEntities.dll")
'Get all classes in the assembly
Dim AllEntities As Type() = assem.GetTypes()
Return AllEntities.FirstOrDefault(Function(e) e.FullName = entityName)
End Function
Then set the grid enable dynamic data based on the result of the function:
Dim EntityType As Type = GetEntity(general_retrieve.gr_entity_set_name)
myGrid.EnableDynamicData(EntityType)

Is assigning an object to itself a good idea?

I have two classes, RecordSet and Record. RecordSet has a Generic List(Of Record).
I can add objects to the list by calling my RecordSet.AddRecord(ObjRecord) function, which returns RecordSet. When the list has a count of 200, some processing occurs and a new RecordSet object is returned, otherwise itself is returned and the application can carry on adding Record objects to the list.
My concern is that there will be 200 objects of RecordSet until garbage collection does it's sweep. Is this a good idea?
Public Class RecordSet
Private lstRecords As New List(Of Record)
Public Function AddRecord(SomeVariable) AS RecordSet
lstRecords.Add(New Record())
If lstRecords.Count = 200 Then
Me.ProcessTheRecords()
Return New RecordSet()
Else
Return Me
End If
End Function
Private Sub ProcessTheRecords()
'Do stuff in here
End Sub
Private Class Record
Public Sub New()
End Sub
End Class
End Class
Then in my application I call:
Dim objRecordSet AS New RecordSet
For Each VariableName In SomeList
objRecordSet = objRecordSet.AddRecord(VariableName)
Next
'Process the remaining objects in objRecordSet here.
First of all, this is really bad pratice, it's hard to follow the code for someone new and is a potential bug source. Instead of returning urself every time, change your design.
Change your function to this:
Public Sub AddRecord(SomeVariable)
lstRecords.Add(New Record()) <--- should't you be doing something with SomeVariable?!
If lstRecords.Count = 200 Then
Me.ProcessTheRecords()
end if
End Function
Private Sub ProcessTheRecords()
'Do stuff in here
Me.lstRecords.clear()
End Sub
Now AddRecord does exactly what it says it does - it adds a new record and modifies the recordSet. ProcessTheRecords does the processing, as its supposed to do, and if u need to clear the list container - oh well, just clear it.
I strongly recommed to read this wiki article about
Cohesion.
Just as a proposiontion, the AddRecord could be a function of return type Boolean, which indicates the success of the operation (maybe an error or exception can be raised by the processing function?).
It's much cleaner now, isn't it?

Must I use parameters with an ObjectDataSource update?

I have a business object that I've been using, which has a bunch of properties and a Save method, which inserts/updates to the database. The save method is NOT status, so the object needs to be instantiated, and the properties for the DB update/insert get pulled from the object.
Now I'm trying to bind the object to a FormView with the ObjectDataSource. I have it working so it instantiates based on the QueryString parameter, no problem, and populates the textboxes just fine. The UpdateMethod I have set to the Save function. Now it gets stuck.
It seems the ObjectDataSource needs a method with all the fields/properties/textboxes as parameters. I would have thought it would update the object's properties and then call the parameterless Save function. Is this wishful thinking?
Do I now need to change my Save function to include parameters, and change all the instances where it's getting used to this new method, just for this reason?
Thanks
Sean
Unfortunatly it does require params.
I overloaded my insert/update methods to include a few params. Attach the ObjectDataSource to the method with params.
The overloaded Update method calls the original Update method saving all the data. Seems kind of hackish to me, but it works.
Public Sub Update()
Dim isUpdated As Boolean = False
sql = "UPDATE AudioFiles SET Title = #Title, [desc] = #desc, Active = #Active WHERE fileID = #fileID"
conn = New SqlConnection(connString)
conn.Open()
...
End Sub
Public Sub Update(ByVal upFileID As Integer, ByVal upTitle As String, ByVal upDesc As String, ByVal upActive As Boolean)
Dim isUpdated As Boolean = False
Dim audioFile As New AudioFiles(fileID)
If Len(upTitle) > 0 Then
_title = title
End If
...
audioFile.Update()
End Sub

Resources