Linq to Sql navigation object instance properties are null or default - vb.net

I'm using Linq to Sql and have a proper foreign key relationship setup in the underlying tables.
However, when I try to use navigation properties I have a subtle bug.
In the code sample below, when I put a watch on the PartDetails, I do get the fully populated parts. However, if I call the property on each part to check their values, the instance is now null.
I've hunted around for the last couple of hours to find an answer but so far coming up dry.
Can anyone clue me in as to why this is happening?
I'm on .net 4.6.1, Visual studio 2015 and Sql Server 2014.
I confess I couldn't find the correct place to fire off the DataLoadOptions but this seemed to work fine!
Partial Public Class LabourDetail
Private Sub OnCreated()
Dim db As New DataContext
Dim ds As DataLoadOptions = New DataLoadOptions()
ds.LoadWith(Function(c As LabourDetail) c.PartDetails)
db.LoadOptions = ds
End Sub
Public ReadOnly Property AnyPartsUnConsumed As Boolean
Get
'If I put a watch on the partdetails I do get a proper collection with proper instances.
Return PartDetails.Where(Function(p) p.PartsUnConsumed).Any
End Get
End Property
End Class
Partial Public Class PartDetail
'When we reach this point, the values in the instance are all Null / Default
Public Property PartsUnConsumed() As Boolean = _CheckPartsUnConsumed()
End Class
I'd be grateful for any assistance!

This Private Sub OnCreated() effectively doesn't do anything. It creates a context that immediately goes out of scope.
I assume there is some context that materializes LabourDetails from the database. That's the context to set the LoadOptions of.

Related

Advantages of Properties in Classes

I've been using classes for a while now, but I feel I may have been using them incorrectly.
When I create the properties for the class, I just use public variables so I end up with something like the following:
Class clsMyClass
Public Name As String
End Class
However, I've been reading some info on the net and they suggest that it should be set up in the following way:
Class clsMyClass
Private Name As String
Property UsersName() As String
Get
Return Name
End Get
Set(ByVal Value As String)
Name = Value
End Set
End Property
End Class
Is the way I'm doing it extremely incorrect? If so, why? I feel like the second method adds some sort of security but to be honest, it just looks like unnecessary code..?
One advantage of properties is that they let you customise the access to your private fields and enable you to do more so you can do the following (examples, it's not limited to that):
Make a property read-only for public access
Raise an even when a property is updated
Update other private fields when a property is updated
Validate the value that is being set
See below advantages of Properties over Variables from the C# in Depth article:
• There's more fine-grained access control with properties. Need it to be publicly gettable but really only want it set with protected access? No problem (from C# 2 onwards, at least).
• Want to break into the debugger whenever the value changes? Just add a breakpoint in the setter.
• Want to log all access? Just add logging to the getter.
• Properties are used for data binding; fields aren't.
Few other points:
1) You can also make properties read-only so no one from outside the class set the values but can fetch it.
2) You can do certain actions in the get and set. i.e. Append a prefix anytime set is called
3) You can also use auto-implemented property to minimize code like below:
Public Property Name As String
You are not doing anything wrong. Properties give you a shorthand basically, a syntactic sugar.
You can still use a backing private variable and do logic in get and set if you have to while using properties. Even better is the private/protected set or get, which is again another syntactic sugar so that you won't have to write all the code manually.
First of all, VB.NET allows you to use this syntax (called shorthand property declaration - I believe since VS 2010):
Public Property Name As String
Not so much different from this (called field declaration):
Public Name As String
Second, Microsoft data binding does not work well with fields. Try this example (see below).
Example. Put a listbox called ListBox1 (default name) and a button called Button1 on an empty form in an empty WinForms project. Replace your form code with this:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim lst As New List(Of clsMyClass)
lst.Add(New clsMyClass)
ListBox1.ValueMember = "Name"
ListBox1.DisplayMember = "Name"
ListBox1.DataSource = lst
End Sub
End Class
Class clsMyClass
Public Property Name As String = "Hello"
End Class
Start the application and notice that a listbox is populated with one entry, Hello. This proves that binding worked correctly. Now replace your property declaration with a field declaration instead. Start your application one more time and notice that a listbox is showing your class type converted to String. It means that your field binding did not work, and default binding was used instead, where DisplayMember is assigned sort of classInstance.ToString().
If you are curious to learn more about what happens behind the scenes, you can put a breakpoint on .DataSource assignment, and see how DisplayMember gets reset or keeps its value depending on whether you are using fields or properties.

Late Binding to a form's public variables

I have 20+ MDI forms with consistently named Public variables. When the child form closes a method on the MDI Parent is called passing Me as a generic form type. How can I access the public variables by name via the Form reference? I only need to read the variables. Of course the Variables() method does not exist...
Public Sub CleanupForm(ByVal frm As Form)
Dim sTable_Name As String = frm.Variables("TABLE_NAME") ' Public at form level
Dim cLock As clsRecLocks
cLock = frm.Variables("Rec_Lock")
cLock.DeleteThisLock()
'..
I've seen some posts on similar requests but most start out with "don't do it that way..." then go off in the weeds not answering the question. I concede it is poor design. I can't change all the calling forms in the short term so I need to use this approach.
VS2010, VB.Net, Win Forms, .Net 2.0
I was able to get to a simple variable using CallByName:
Try
Dim s As String = CallByName(frm, "TABLE_NAME", CallType.Get)
Stop
Catch ex As Exception
MsgBox(ex.Message)
End Try
On to the class object. Perhaps I can add a default Get for the class that returns the ID I need.
Default property won't work as the Locks object was not declared Public - at least for the CallByName() approach.
Can Reflection get to form level variables not declared Public? Seems like a security issue, but...
Can I get a "Parent" reference in the instantiated Locks class? i.e. A reference to the form that established the Locks object? I can change the clsRecLocks() class.
I found a property I could get to that told me the form was "read-only" and I can use that tidbit to delete the correct (or more correct - still not 100%) lock record. So the bug is 90% fixed. I think I need update all the forms with code that records the info I need to get to 100%.
Thanks to all!
Poor design but you can do this:
Public Sub CleanupForm(ByVal frm As Form)
Dim frmTest as object = frm
? = frmTest.TABLE_NAME
? = frmTest.Rec_Lock
End Sub
This will compile and if the variables exist, it will return them but if not, you get an error.
Converting to an interface after the fact is not that hard, you should do it now rather than later.

Expose .NET DataTable properties to VBA via COM Interface

I am trying to create a .Net DLL basically as an abstraction layer for database connections; it is going to replace a current DLL we have that is written in VB6 and I am trying to match the current functionality as much as possible.
Anyway, the essential issue I am having is that I can't find a way to get .Net classes like DataColumnCollection or DataColumn to display in the VBA Interpreter -- It may say, for example, "Column" with the type "MarshalByValueComponent," but the value will be "No Variables".
I can get it to work if I completely re-create both classes (i.e. Fields as a collection of field, which inherits from DataColumn, and then define an interface for both), but that seems like a lot of added overhead for what (should be?) a pretty simple idea. I feel like I am just missing something very simple with the way the marshaller is handling the DataColumn class.
A lot of the stuff I am finding online is on how to convert a DataTable or DataReader to a legacy ADODB Recordset, but that also would add a lot of overhead... I'd rather leave it as a DataTable and create a COM interface to allow VBA to interact with it; that way if, for example, they want to write the table to an excel sheet, I wouldn't be duplicating work (convert to ADODB recordset, then read/write to excel sheet. You'd need to iterate the entire table twice...)
Sorry for the book-length explanation -- I felt the problem needed a bit of clarification since the root-cause is trying to match legacy functionality. Here is an example of my current interface that does not work:
Public Interface IDataTable
ReadOnly Property Column As DataColumn
End Interface
<ClassInterface(ClassInterfaceType.None)> _
<System.ComponentModel.DesignerCategory("")> _
<ComDefaultInterface(GetType(Recordset.IDataTable))> _
<Guid("E7AFBBB6-CB20-44EC-9CD2-BC70B94CD8B7")> _
Public Class Recordset : Inherits Data.DataTable : Implements IDataTable
Public ReadOnly Property Column As DataColumn Implements IDataTable.Column
Get
Return MyBase.Columns(0)
End Get
End Property
Note: I originally tried the property Columns as DataColumnCollection which returned MyBase.Columns. That came through as an Object, instead of MarshalByValueComponent, but was also empty. I know MyBase.Column(0) has a value, because I can put Msgbox(MyBase.Columns(0).ColumnName) right above the return in the get and it pops up fine (don't judge; this is way easier than using a debugger for this)...
I wouldn't mind just defining them both, but I can't inherit DataColumnCollection and the COM interface already sucks at dealing with generics. Is there any other way around this without re-inventing the wheel?
Thanks for your help!
I just spent the last 3 weeks doing something eerily similar.
I ended up making two .NET assemblies:
A pure .NET assembly that talks to the datastore (for use by .NET apps).
A "COM Interop" assembly that wraps the first assembly and adds the COM overhead (ADODB references and COM-Visible interfaces).
I call the second assembly from Excel VBA using the VSTO "AddIn.Object" property.
I ended up converting System.Data.DataTables to ADODB.Recordsets as you mentioned. Getting .NET and VBA talking about anything other than primitive types was beyond-frustrating for me. In fact, I ended up serializing some objects as JSON so the two worlds could communicate.
It does seem insane, but I reinvented the wheel.
I followed this MSDN article to make my .NET code callable by VBA.
I used this Code Project article (I'm sure you've seen) to convert to Recordset*.
I let the frameworks handle string, integers, etc.
For all other data types I used Json.Net and a custom VBA class to do JSON serialization.
*Converted article to VB.Net and added some extra error handling.
Okay, this probably isn't the most elegant (or complete, at this point) solution; but I think it's the route I am going to go.
Instead of converting the whole thing to an ADODB Recordset (and duplicating any iterations), I just threw out the DataTable class entirely and wrote my own Recordset class as a COM Wrapper for the a generic Data Reader (via the IDataReader interface) and added a new Field class to manage the type conversion and set up Fields as an array of Field (since interop hates generics)
It basically creates a forward-only ADODB Recordset (same limitations) but has the benefit of only loading one row at a time, so the bulk of the data can be handled as managed code until you know what they want to do with it (I'm going to add methods for ToArray, ToAccessDB, ToFile, etc that use the reader) while still allowing the ability to iterate through the Recordset from excel/access/vbscript/vb6 (if that's really what they want to do.. mostly needed that for legacy support anyway)
Here is an example, in case anyone else has to do this again; somewhat modified for brevity:
Public Interface IRecordset
ReadOnly Property CursorPosition As Integer
ReadOnly Property FieldCount As Integer
ReadOnly Property Fields As Field()
Function ReadNext() As Boolean
Sub Close()
End Interface
<System.ComponentModel.DesignerCategory("")> _
<ClassInterface(ClassInterfaceType.None)> _
<ComDefaultInterface(GetType(IRecordset))> _
<Guid("E7AFBBB6-CB20-44EC-9CD2-BC70B94CD8B7")> _
Public Class Recordset : Implements IRecordset : Implements IDisposable
Private _Reader = Nothing
Private _FieldCount As Integer = Nothing
Private _Fields() As Field
Public ReadOnly Property CursorPosition As Integer Implements IRecordset.CursorPosition...
Public ReadOnly Property FieldCount As Integer Implements IRecordset.FieldCount...
Public ReadOnly Property Fields As Field() Implements IRecordset.Fields...
Friend Sub Load(ByVal reader As IDataReader)
_Reader = reader
_FieldCount = _Reader.FieldCount
_Fields = Array.CreateInstance(GetType(DataColumn), _FieldCount)
For i = 0 To _FieldCount - 1
_Fields(i) = New Field(i, Me)
Next
End Sub
'This logic kinda sucks and is dumb.
Public Function ReadNext() As Boolean Implements IRecordset.ReadNext
_EOF = Not _Reader.Read()
If _EOF Then Return False
_CursorPosition += 1
For i = 0 To _FieldCount - 1
_Fields(i)._Value = _Reader.GetValue(i).ToString
Next
Return True
End Function
From here you just need to define some type like Field or Column and add an interop wrapper for that type:
Public Interface IField
ReadOnly Property Name As String
ReadOnly Property Type As String
ReadOnly Property Value As Object
End Interface
<System.ComponentModel.DesignerCategory("")> _
<ClassInterface(ClassInterfaceType.None)> _
<Guid("6230C670-ED0A-48D2-9429-84820DC2BE6C")> _
<ComDefaultInterface(GetType(IField))> _
Public Class Field : Implements IField
Private Reader As IDataReader = Nothing
Private Index As Integer = Nothing
Public ReadOnly Property Name As String Implements IField.Name
Get
Return Reader.GetName(Index)
End Get
End Property
Public ReadOnly Property Value As Object Implements IField.Value
Get
Return Reader.GetValue(Index)
End Get
End Property
Public ReadOnly Property Type As String Implements IField.Type
Get
Return Reader.GetDataTypeName(Index).ToString
End Get
End Property
Sub New(ByVal i As Integer, ByRef r As IDataReader)
Reader = r
Index = i
End Sub
End Class
All of this is rather silly, but it seems to work well.
Note: I've only been using .Net for about 4 days now, so this might be terrible, please feel free to comment on anything extremely stupid I might be doing.

Active Record with Entity Framework

I'm working on a project that was built using ADO.NET (raw sql) and Active Record pattern. I am slowly moving it away from ADO.NET to Entity Framework Code First 4.3.
Here is an example of the pattern. The interface is a static Load and an instance Save (and Delete -- not shown).
Public Class Part
Public Property Id as Integer
Public Shared Function Load(_id As Integer) As Part
Using context As New DataContext()
Return context.Find(_id)
End Using
End Function
Public Sub Save()
Using context As New DataContext()
If Id = 0 Then
context.Parts.Add(Me)
Else
context.Entry(Me).State = Data.EntityState.Modified
End If
context.SaveChanges()
End Using
End Sub
End Class
I realize Active Record is not ideal for EF but I'd like to make it work to remove all of the ADO.NET code while not touching the rest of the code.
This mostly works, but I've run into an issue I don't know how to solve. In order to keep Foreign Keys in sync we handle it like such:
Public Sub Save()
ParentPart = Part.Load(ParentPartId)
ChildPart = Part.Load(ChildPartId)
Using context = New iTracContext()
If bid = 0 Then
context.BillOfMaterials.Add(Me)
Else
context.Entry(Me).State = Data.EntityState.Modified
End If
context.SaveChanges()
End Using
End Sub
This makes sure EF doesn't complain that we have non-matching relationships -- the Id always wins.
The issue is that its throwing an exception now when I save.
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
This is thrown from the line:
context.Entry(Me).State = Data.EntityState.Modified
How is anything in the ObjectStateManager for this context? It is brand new and should be empty, no?
If I remove the two Part.Load(...) lines it works fine.
Is there some type of change tracker that lives outside the context that I'm not aware of? That seems like it would kill any attempt at the Active Record pattern.
I'm also open to any suggestions on how to make Active Record work with EF. The context.Entry line is terrible but I don't know what else to do.
Telling me not to do Active Record isn't helpful, but feel free.
I believe Entity Framework may still be tracking the object from the context you loaded it from, because you create a new context for each Load and Save call. If this is the case, try detaching the objects after you load them:
Public Shared Function Load(_id As Integer) As Part
Using context As New DataContext()
Part part = context.Find(_id)
context.Entry(part).State = EntityState.Detached ' Detach from the initial context
Return part
End Using
End Function

LINQ to SQL: Is it possible to reference the data context when extending a table object?

If I'm extending OnCreated for a LINQ to SQL table object, is it possible to get a reference to the data context to which the table belongs? For example, if I add a property to the data context:
Partial Class MyDataContext
Private _myValue As String
Public ReadOnly Property MyValue As String
Get
Return _myValue
End Get
Set(ByVal value As String)
_myValue = value
End Set
End Property
End Class
is there any way to access that value in the create event of the table, i.e.:
Partial Class MyTable
Private Sub OnCreated()
Dim contextValue = [data_context_reference_here].MyValue
End Sub
End Class
I don't want the property on the data context to be Shared, because it could be different per instance. I've been pouring over the designer code to figure out where the reference might be, but no luck yet. Any ideas?
There are no generated table objects in LINQ to SQL--only row objects, of which the DataContext owns one Table(Of TRowType) per. So the partial OnCreated method you implement is in a row class, and will be called whenever a row is created.
Generated row objects implement INotifyPropertyChanged and INotifyPropertyChanging, but are not derived from any base class. Since OnCreated takes no parameters, there is no way for the row to determine (via that method) which Table it belongs to, much less which DataContext it was created for.
You'll have to find some other way to do what you want.
I will say it is an incorrect pattern for Linq 2 SQL
In Linq2sql, it is supposed that any row object like your MyTable can be created without context. Later you can attach it to a table (with a specified context)
For example
Dim myTable as new MyTable()
dataContext.GetTable(Of MyTable).Attach(myTable)
So in reality, you should not create any logic inside MyTable classes that will depend on datacontext that was used to create this object, because some of them can be created without dataContexts, some can be attached and detached...
A simple google search (linq event data context) would have led you to where this question was already asked and answered:
Determine the source DataContext for a Linq to Sql query