SQL table join to VB.net class object - sql

I'm a web programmer transitioning to vb.net programming and I am in need of proper guidance. What I am trying to do is perform an SQL table join as shown in the reference here. It's my understanding that it's a good practice to for OOP to create a Class Object for each "type" of table in SQL.
For example, In the link in Reference I would have the following classes in .net.
Public Class Orders
Private _OrderID As GUID
Private _CustomerID As GUID
Private _OrderDate As Date
Public Property OrderID As Guid
Get
Return _OrderID
End Get
Set(ByVal value As Guid)
_OrderID = value
End Set
End Property
Public Property CustomerID As Guid
Get
Return _CustomerID
End Get
Set(ByVal value As Guid)
_CustomerID = value
End Set
End Property
Public Property OrderDate As Date
Get
Return _OrderDate
End Get
Set(ByVal value As Date)
_OrderDate = value
End Set
End Property
Public Sub New(ByVal obj As Object)
End Sub
End Class
and
Public Class Customers
Private _CustomerID As GUID
Private _CustomerName As String
Private _ContactName As String
Private _Country As String
Public Property CustomerID As Guid
Get
Return _CustomerID
End Get
Set(ByVal value As Guid)
_CustomerID = value
End Set
End Property
Public Property CustomerName As String
Get
Return _CustomerName
End Get
Set(ByVal value As String)
_CustomerName = value
End Set
End Property
Public Property ContactName As String
Get
Return _ContactName
End Get
Set(ByVal value As String)
_ContactName = value
End Set
End Property
Public Property Country As String
Get
Return _Country
End Get
Set(ByVal value As String)
_Country = value
End Set
End Property
Public Sub New(ByVal obj As Object)
End Sub
End Class
So given the two classes in .NET, how can I query my SQL with a table join and still maintain a proper OOP approach? Should I create a 3rd class which combines both tables?
Like This...
Public Class CustomerOrdersCombined
Private _OrderID As GUID
Private _CustomerID As GUID
Private _OrderDate As Date
Private _CustomerName As String
Private _ContactName As String
Private _Country As String
Public Property OrderID As Guid
Get
Return _OrderID
End Get
Set(ByVal value As Guid)
_OrderID = value
End Set
End Property
Public Property CustomerID As Guid
Get
Return _CustomerID
End Get
Set(ByVal value As Guid)
_CustomerID = value
End Set
End Property
Public Property OrderDate As Date
Get
Return _OrderDate
End Get
Set(ByVal value As Date)
_OrderDate = value
End Set
End Property
Public Property CustomerName As String
Get
Return _CustomerName
End Get
Set(ByVal value As String)
_CustomerName = value
End Set
End Property
Public Property ContactName As String
Get
Return _ContactName
End Get
Set(ByVal value As String)
_ContactName = value
End Set
End Property
Public Property Country As String
Get
Return _Country
End Get
Set(ByVal value As String)
_Country = value
End Set
End Property
Public Sub New(ByVal obj As Object)
End Sub
End Class
What is the best way to do this as an object? Let me know if this post is kind of confusing. I'm not sure if I made my question very clear. Thanks in advance.

You're falling into the OO purity trap.
Don't go for that trap where you query for a collection, then iterate over the collection to do the JOIN. That's the n+1 latency trap. You'll die a slow, non-performance death doing things like that. Objects aren't buying you anything.
Databases have been optimized to do JOINs very well; let them. Do the JOIN in SQL and sort out the mapping into objects that way.
An Order object might have a collection of LineItem objects underneath. Bring them all back in one query and map them once you've got the data.

IMO you should not "always" think that everything you do, you must create a new object especially in this kind of situation regarding tables in .NET. I suggest learning ADO.NET first and you will see there DataTables class that you can use. Also, please study LINQ as you will be captivated by this when working with sql queries.

First of all, yes this question is a bit confusing, mainly because it seems you're showing objects and asking about an INNER JOIN SQL statement into an object.
That being said I thing it seems that you have 2 tables in your Database Customers and Orders. I am assuming that a customer can have many orders, but an order can have only one customer making Orders and Customers have a 1-N relationship (one-to-many).
Now that I think I understand your question I will go into a bit of explaining why you shouldn't have a third object and the proper way to solve the problem. The main reason for normalizing your database and having 2 separate tables is that you don't want your Orders table also containing all the information for your customers. Now I'm assuming that the SQL query for your INNER JOIN would look something like this
SELECT
*
FROM
Customers INNER JOIN Orders
ON Customers.CustomerID = Orders.CustomerID
WHERE
Customers.CustomerID = #CustomerID
This would mean your are pulling all the data back for the same customer for each order. This means that if a customer has 100 orders you would in essence be creating 100 instances of your CustomerOrdersCombined Class and this would be in addition too your instance of customers class and 100 instances of your Orders Class. Hopefully you can see why this is a bad idea.
Now for how I would solve this problem.
Way one Make a method:
One way to solve the issue would be to create a method in your Customers class that would return all the Orders that are related to the customer
Public Function GetCustomerOrders() As List(Of Orders)
Return (New Orders).GetOrdersByCustomerID(Me.CustomerID)
End Function
The function "GetOrdersByCustomerID" would be a constructor for the Orders Class that returns a list of Orders by a CustomerID The SQL for that would be something like
SELECT * FROM Orders WHERE CustomerID = #CustomerID
Way 2 of solving your problem:
Orders is a property of your Customer Class, this may sound confusing but think of this assume that before your the instance of Customer class dies you want to look at the customers orders several times. In the above solution every time you want to look a the orders you have to hit the data base, this is in efficient and can be handled better. By making the customers orders a property of the Customer class, that way lives in memory until the customer object dies so you only have to hit the database once. Now you might be thinking what if I want to get information about a customer and not their 100 orders, why should I look that too. Well you shouldn't! There is a method called "Lazy Loading" This mean that you only load the order if/when they're requested. So This is how I would design the Customer Class
Public Class Customers
Private m_CustomerID As GUID
Private m_CustomerName As String
Private m_ContactName As String
Private m_Country As String
Private m_Orders As List(Of Orders)
Public Property CustomerID As Guid
Get
Return _CustomerID
End Get
Set(ByVal value As Guid)
_CustomerID = value
End Set
End Property
Public Property CustomerName As String
Get
Return _CustomerName
End Get
Set(ByVal value As String)
_CustomerName = value
End Set
End Property
Public Property ContactName As String
Get
Return _ContactName
End Get
Set(ByVal value As String)
_ContactName = value
End Set
End Property
Public Property Country As String
Get
Return _Country
End Get
Set(ByVal value As String)
_Country = value
End Set
End Property
Public Property Orders AS List(Of Orders)
Get
If m_Orders Is Nothing Then
'Only get the orders if requested'
m_Orders = (New Orders).GetOrdersByCustomerID(Me.CustomerID)
End If
Return m_Orders
End Get
Set(ByVal value As List(Of Orders))
m_Orders = value
End Set
End Property
Public Sub New(ByVal obj As Object)
End Sub
End Class
Notice in the getter is where the value is set, so otherwise orders with be a blank value. I hope this long answer is helpful to you.

Related

Can't iterate through each property of a custom object

I have this class:
Public Class clsServCasam
Public ID As Long
Public CANT As Decimal
Public PRICE As Decimal
End Class
I create a variable of that type and get the object from an API result:
Dim myObj As clsServCasam()
Dim rsp As HttpWebResponse = CType(rq.GetResponse(), HttpWebResponse)
If rsp.StatusCode = HttpStatusCode.OK Then
Using sr = New StreamReader(rsp.GetResponseStream())
myObj = JsonConvert.DeserializeObject(Of clsServCasam())(sr.ReadToEnd())
End Using
Then I try to get the field names from the object:
For Each p As System.Reflection.PropertyInfo In myObj.GetType().GetProperties()
Debug.Print(p.Name, p.GetValue(myObj, Nothing))
Next
But, instead of class fields (ID, PRICE, ...) I got:
- Length
- LongLength
- Rank
Update
As Steven Doggart pointed out, the above loop won't work because it looks for properties rather than fields. So, I tried changing the loop to this:
For Each p As FieldInfo In myObj.GetType.GetFields()
Debug.Print(p.Name)
Next
But now I'm getting no results at all.
In your code, myObj is not declared as clsServCasam. Rather, it is declared as clsServCasam(), which means it's an array of clsServCasam objects. So, when you use reflection to iterate over its properties, you're getting the properties of the array rather than the actual clsServCasam type.
For instance, this would work more like you're expecting:
For Each item As clsServCasam in myObj
For Each p As PropertyInfo In item.GetType().GetProperties()
Debug.Print(p.Name, p.GetValue(item, Nothing))
Next
Next
However, I think you'll find that that still won't work because it iterates over the properties rather than the fields. In the definition of the clsServCasam class, all of the members are fields rather than properties, so the only properties that it have would be ones that are inherited from Object. You will need to either iterate over the fields using GetFields, like this:
For Each item As clsServCasam in myObj
For Each f As FieldInfo In item.GetType().GetFields()
Debug.Print(f.Name, f.GetValue(item))
Next
Next
Or you'll need to change them to properties:
Public Class clsServCasam
Public Property ID As Long
Public Property CANT As Decimal
Public Property PRICE As Decimal
End Class
Or, if you are using an older version of the VB compiler which doesn't support auto-properties:
Public Class clsServCasam
Public Property ID As Long
Get
Return _id
End Get
Set(value As Long)
_id = value
End Set
End Property
Public Property CANT As Decimal
Get
Return _cant
End Get
Set(value As Decimal)
_cant = value
End Set
End Property
Public Property PRICE As Decimal
Get
Return _price
End Get
Set(value As Decimal)
_price = value
End Set
End Property
Private _id As Long
Private _cant As Decimal
Private _price As Decimal
End Class

how to append one class to another of same in vb.net

PropertyPolicy is a class that defines a collection of several fields/entities. Sometimes two separate functions are needed to build out the collection. (LoadEstateAIN and LoadAIN). I need to combine the results of both classes but have tried concat but get a cast exception. What would work here?
Public Class PropertyPolicy
Private agentfield As Entity
Private aininsuredfield() As Entity
Private billinginfofield As BillingInfo
Private cancellationdatefield As Date
Private claimsfield() As Claims
Public Property Agent As Entity
Get
Return Me.agentfield
End Get
Set(ByVal value As Entity)
Me.agentfield = value
End Set
End Property
Public Property AINInsured() As Entity()
Get
Return Me.aininsuredfield
End Get
Set(ByVal value As Entity())
Me.aininsuredfield = value
End Set
End Property
Public Property BillingInfo As BillingInfo
Get
Return Me.billinginfofield
End Get
Set(ByVal value As BillingInfo)
Me.billinginfofield = value
End Set
End Property
Public Property CancellationDate As Date
Get
Return Me.cancellationdatefield
End Get
Set(ByVal value As Date)
Me.cancellationdatefield = value
End Set
End Property
Public Property Claims() As Claims()
Get
Return Me.claimsfield
End Get
Set(ByVal value As Claims())
Me.claimsfield = value
End Set
End Property
End Class
Dim propTemp1 As New PropertyPolicy
Dim propTemp2 As New PropertyPolicy
Dim propTempComb As New PropertyPolicy
propTemp1.AINInsured = LoadEstateAIN(policyid, asofDate, lob, NINclientid, estatecompany)
propTemp2.AINInsured = LoadAIN(policyid, asofDate, lob, NINclientid, estatecompany)
propTempComb.AINInsured = propTemp1.AINInsured.Concat(propTemp2.AINInsured)
The result of Concat is not an array; it is an IEnumerable(Of T). In your case it is an IEnumerable(Of Entity). You just need to add ToArray() to the end of the Concat if you want to assign it back to an array.
propTempComb.AINInsured = propTemp1.AINInsured.Concat(propTemp2.AINInsured).ToArray()
Breaking down this line of code:
[instance3].[property] = [instance1].[property].Concat([instance2].[property])
Assigns the result of the Concat to the property, but the property is an array so you need to change the result of Concat which is an IEnumerable(Of Entity) into an array which is trivial with ToArray.
I could go further and recommend that you don't use arrays as public members, rather IEnumerable. Also, auto-properties would be a better choice for some of these Public/Public properties.
Public Class PropertyPolicy
Private aininsuredfield As Entity()
Private claimsfield As Claims()
Public Property Agent As Entity
Public Property BillingInfo As BillingInfo
Public Property CancellationDate As Date
Public Property AINInsured() As IEnumerable(Of Entity)
Get
Return aininsuredfield
End Get
Set(value As IEnumerable(Of Entity))
aininsuredfield = value.ToArray()
End Set
End Property
Public Property Claims() As IEnumerable(Of Claims)
Get
Return claimsfield
End Get
Set(value As IEnumerable(Of Claims))
claimsfield = value.ToArray()
End Set
End Property
End Class
And btw, that would cause your original code to work without ToArray()
propTempComb.AINInsured = propTemp1.AINInsured.Concat(propTemp2.AINInsured)

Predefined class properties if need expand or shrink quantity of them on each app start

Public Class Products
Private zCriteria As String
Public Property Criteria As String
Get
Return zCriteria
End Get
Set(value As String)
zCriteria = value
End Set
End Property
Private zProductList As New List(Of Product)
Public Property ProductList As List(Of Product)
Get
Return zProductList
End Get
Set(value As List(Of Product))
zProductList = value
End Set
End Property
End Class
Public Class Product
Private zCriteriaList As List(Of Criterias)
Public Property CriteriaList As List(Of Criterias)
Get
Return zCriteriaList
End Get
Set(value As List(Of Criterias))
zCriteriaList = value
End Set
End Property
End Class
Public Class Criterias
Private zCrPropName As String
Public Property CrPropName As String
Get
Return zCrPropName
End Get
Set(value As String)
zCrPropName = value
End Set
End Property
Private zCritCode As String
Public Property CritCode As String
Get
Return zCritCode
End Get
Set(value As String)
zCritCode = value
End Set
End Property
Private zcrPropValue As String
Public Property crPropValue As String
Get
Return zcrPropValue
End Get
Set(value As String)
zcrPropValue = value
End Set
End Property
End Class
For Each oProducts As Products In oAssigment.ProductsList
For Each oproduct As Product In oProducts.ProductList
For Each cr As Criterias In oproduct.CriteriaList
cr.CrPropName = "Product Name" 'some object property name
cr.CritCode = "PN"
cr.crPropValue = "" ' Value of property "Product Name"
Next
Next
Next
It is all made to have different properties of some object depending on options set in text file. It is only a sample o usage.
conditions:
Criterias on applications start is same for all objects (=Product) i want to read.
Every time on start, application read options file where is defined few property names and codes (values i want get from objects). So every run can be initiated different quantity of properties to read. It means i can not have hard-coded names of properties.
Someone can advice me how to predefine "CrPropName" and "CritCode" on app start so many times how much property names defined in options and after that populate it so many times as how many objects exist from which i read these property values.
p.s. I am not very professional at coding and sorry for my language

LongListSelector selecteditem

I have a LongListSelector in an .xaml and I am able to fill it by binding to a an ItemSource when the source is filled by a DataContext using a single table from my SQL Server CE database like this:
Dim row = (From rows In db.Hub
Order By rows.HubID Descending
Select rows).ToList()
Me.MainLongListSelector.ItemsSource = row
I am thus able to get the ID of the selected item as follows:
HubID = CType(MainLongListSelector.SelectedItem, Hub).HubID
I am also able to bind to a 'query' DataSource as follows:
Dim row = (From ac In db.Activity
Join at In db.ActivityType On ac.ActivityTypeID Equals at.ActivityTypeID
Select New With {.ID = ac.ActivityID,
.Title = ac.Activity1}).ToList()
Me.MainLongListSelector.ItemsSource = row
however, since this is not referring to a specific table in the DataContext, I cannot get the ID using the above code, ie:
Dim ActID = CType(MainLongListSelector.SelectedItem, Activity).ActivityID '- returns nothing
How should I get the value(s) of selectedItem in this case?
NB: I have created the anonymous fields (.ID and .Title) because those are the names I have bound in the xaml, so the LongListSelected gets populated without writing extra code.
Thanks
Phew!!
I discovered that two things:
this HubID = CType(MainLongListSelector.SelectedItem, Hub).HubID is calling a List (Of DataContext), while in the second scenario above I am using a List (Of Anonymous). So I searched for List (Of Anonymous) and this came up!
I now know I can create a class for List (Of Anonymous) and properly name its properties, thus make it available outside its methods, like in my 'query' question above.
So the answer is I created the class for my anonymous list, declared its properties
Public Class AnonList
Private _id As Integer
Public Property ID() As Integer
Get
Return _id
End Get
Set(ByVal value As Integer)
_id = value
End Set
End Property
Private _title As String
Public Property Title() As String
Get
Return _title
End Get
Set(ByVal value As String)
_title = value
End Set
End Property
Private _desc As String
Public Property Desc() As String
Get
Return _desc
End Get
Set(ByVal value As String)
_desc = value
End Set
End Property
End Class
and therefore assigned them to the ItemSource values,
Select New AnonList With {.ID = ac.ActivityID,
thus being able to get the SelectedItem values as required:
ActivityID = CType(MainLongListSelector.SelectedItem, AnonList).ID
Took a bit of determination to figure that out!

Unable to bind 2 tables to viewModel and send to view

table 1. MerchantBusiness
table 2. MerchantServices
i want to display both tables data in a single view.
I want to display the MerchantBusiness details on the view. very basic business name, address etcc.
the MerchantServices is a list of services that the business has.
so i first created the viewModel
Public Class MerchantServicesModel
Public Property MerchantViewModel() As Merchant
Get
Return _Merchant
End Get
Set(value As Merchant)
_Merchant = value
End Set
End Property
Private _Merchant As Merchant
Public Property ServiceViewModel() As IEnumerable(Of MerchantService)
Get
Return _Services
End Get
Set(value As IEnumerable(Of MerchantService))
_Services = value
End Set
End Property
Private _Services As MerchantService
End Class
and in my controller im binding the two tables to the viewModel
Function Index(id As Integer) As ActionResult
Dim db As New MerchantEntities
Dim MerchantDetails As New MerchantServicesModel
MerchantDetails.MerchantViewModel = db.Merchants.Find(id)
MerchantDetails.ServiceViewModel = (From m In db.MerchantServices
Where m.MerchantID = id
Select m).ToList()
Return View(MerchantDetails)
End Function
My View
#ModelType MerchantServicesModel
#Code
Layout = "~/Views/Shared/_Layout.vbhtml"
End Code
#Html.DisplayFor(Function(model) model.MerchantViewModel.BusinessName)
#For Each item In Model.ServiceViewModel
#Html.DisplayFor(Function(model) item.ServiceName)
Next
i was thinking that the MerchantBusiness is not a full list just details of the particular business
but the MerchantServices is a IEnumerable list so i create a table and style blah blah..
now the error im getting is
Additional information: Unable to cast object of type 'System.Collections.Generic.List`1[VBClassMVC.MerchantService]' to type 'VBClassMVC.MerchantService'.
am i missing something, or am i binding incorrectly?
thank you.
Hay all i manged to fix that. Here is the updated ViewModel
Public Property ServiceViewModel() As IEnumerable(Of MerchantService)
Get
Return _Services
End Get
Set(value As IEnumerable(Of MerchantService))
_Services = value
End Set
End Property
Private _Services As IEnumerable(Of MerchantService)
i needed to add IEnumerable(Of MerchantService) to the _Services Private variable..