DataContext Disposal error resolved but why? - asp.net

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.

Related

Error when using the WHERE clause in Linq-to-SQL

I have a datacontext that I'm trying to query, the results of which I want to bind to a gridview on a button click. Getting connected to the datacontext works great. I get the 1000s of records I expect. When I try to add the WHERE clause, I run into problems. Here's the button event I'm trying to make it happen at:
Protected Sub btnSearch_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim dsource = New MY_DataContext().SV_XXXs '--- This works, the data is all there
gridRec.DataSource = dsource.ToList().Where(Function(dtable) dtable.PN = Session("PN")) '--- this fails
'--- Also tried this, it also did not work ----------------------------------------------------------
'--- gridRec.DataSource = dsource.Where(Function(dtable) dtable.PN = Session("PN")) '--- this fails
'----------------------------------------------------------------------------------------------------
gridRec.DataBind()
End Sub
The session variable is valid and the dsource is populating correctly, but I get the following error when it tries to execute the Where clause:
Evaluation of method
System.Linq.SystemCore_EnumerableDebugView`1[SV_REC].get_Items() calls
into native method System.WeakReference.get_Target(). Evaluation of
native methods in this context is not supported.
Also tried:
Dim results =
(
From T In dsource
Where T.PN = Session("SAFRReceiverPN")
Select T
).ToList
And get this error
Method 'System.Object CompareObjectEqual(System.Object, System.Object,
Boolean)' has no supported translation to SQL.
And tried:
Dim results = From t In dsource Where (t.PN = Session("SAFRReceiverPN")) Select t
nothing seems to work for me when trying a WHERE clause
C# or VB.NET are both cool if you have any suggestions.
Really, any help is appreciated, thanks.
LINQ to SQL doesn't know what to do when you try to access the session inside the query. Instead of doing that, fetch the value from the session before the query and store the result in a local variable, then use that local variable in your query. For example, in C#:
var receiver = (string) Session["SAFRReceiverPN"];
var results = dsource.Where(t => t.PN == receiver);
(I don't bother with query expressions when you're just trying to perform a simple filter.)

Data gets overwritten when adding class object to generic list

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());

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?

Save changes to Entity model to the database

I'm new to Entity Framework and am expanding an existing codebase. I'm using jQuery to pass the needed info back to the server ajaxy style, so I can't use TryUpdateModel(). Here's the code:
<HttpPost()>
Function UpdateRoster() As JsonResult
Dim model As New Models.ViewModels.PlayerAdmin
Dim jsonString As String = Request.Form("json")
model = Deserialise(Of Models.ViewModels.PlayerAdmin)(jsonString)
For Each playerAdminPlayer As Models.ViewModels.PlayerAdminPlayer In model.Roster
Dim playerToTeam As New DAL.PlayersToTeam
Dim player As DAL.Player = PlayerAdminManager.GetPlayerById(playerAdminPlayer.PlayerId)
player.FirstName = playerAdminPlayer.FirstName
PlayerAdminManager.SaveChanges()
Next playerAdminPlayer
Dim playerAfter As DAL.Player = PlayerAdminManager.GetPlayerById(model.Roster.First.PlayerId)
Return Json(New With {.success = False, .message = playerAfter.FirstName})
End Function
Deserialise is a helper function that converts the incoming JSON string to a vb object.
Things seem to work fine in that player successfully loads from the DB and playerAdminPlayer is the correct object from the JSON string. However, when I call PlayerAdminManager.SaveChanges() (which just passes the call the db.SaveChanges() the result is always 0, even if there is a change (not sure if that is expected).
playerAfter was my attempt to see if changes were actually being saved. It seems to work correctly, in that playerAfter.FirstName is the newly updated first name.
PlayerAdminManager.GetPlayerById(integer) pulls from the DB, so I would think that, since changes are observed in playerAfter, that those changes were saved to the DB. However, when I reload the web page (which pulls from the DB), the old values are there.
Any ideas?
Here are some of the functions I mention:
Function GetPlayerById(ByVal Id As Integer) As DAL.Player
Return Container.Players.Where(Function(o) o.PlayerId = Id And o.IsVisible = True).SingleOrDefault
End Function
Sub SaveChanges()
Dim numberOfChanges As Integer = Container.SaveChanges()
Debug.WriteLine("No conflicts. " & numberOfChanges.ToString() & " updates saved.")
End Sub
EDIT
Container code:
Private _Container As DAL.LateralSportsContainer
Protected ReadOnly Property Container As DAL.LateralSportsContainer
Get
If _Container Is Nothing Then
Dim connStr As New System.Data.EntityClient.EntityConnectionStringBuilder
connStr.ProviderConnectionString = Web.Configuration.WebConfigurationManager.ConnectionStrings("ApplicationServices").ConnectionString
connStr.Metadata = "res://*/Lateral.csdl|res://*/Lateral.ssdl|res://*/Lateral.msl"
connStr.Provider = "System.Data.SqlClient"
_Container = New DAL.LateralSportsContainer(connStr.ConnectionString)
End If
Return _Container
End Get
End Property
Turns out I was using a non static (shared) Container. I had 2 Manager classes that both inherited from a BaseManager class were the Container was defined. I was executing the query command in one Manager and saving in another.
Doh!

updating properties of a class with dynamic references

Sorry for what is probably a very basic question. I have a vb.net class with properties defined as follows:
Private m_Property1 As String
Public Property Property1() As String
Get
Return m_Property1
End Get
Set(ByVal value As String)
If IsNothing(value) Then
m_Property1 = String.Empty
Else
m_Property1 = value
End If
End Set
End Property
I can then set the values as follows:
classname.Property1 = "myvalue"
How do I set the value of a property that is defined dynmically eg
Dim strPropertyName As String = "Property1"
Hope that makes sense.
Thanks,
Josh
You would use reflection
Dim strPropertyName as string = "Property1"
Dim pi As PropertyInfo = myClass.GetType().GetProperty(strPropertyName)
pi.SetValue(myClass.GetType(), "some string", Nothing)
You want to use Reflection in order to do this. VB.NET provides a way to do this if you know the value at compile-time, but for run-time operations, you need to use the GetType keyword in order to get the type of your class (or, use the GetType method on an instance of it if you don't know it).
Then, with that Type instance, you would call GetProperty, passing the string with the name of the property. It will return an PropertyInfo instance which you then call GetValue on, passing the instance of the object in, which will return an Object which you have to cast back to a type you wish to use (if you are).
VB.NET makes a lot of this easier with the CallByName function.
Also, if you know at compile-time what the name of the property is, you can always cast to object and use VB.NET's inherent late binding:
Dim o As Object = <your object>
o.Property1 = ...
VB.NET will perform the late-binding for you.

Resources