Is there any way to get this to work? Here's a simplified/contrived illustration of my issue (Pardon my wordy VB):
Domain Model Classes
Public Class Car
Public Property Id As Integer
Public Property Seats As IEnumerable(Of Seat)
End Class
Public MustInherit Class Seat
End Class
Public Class StandardSeat
Inherits Seat
Public Property Manufacturer As String
End Class
Public Class CustomSeat
Inherits Seat
Public Property Installer As String
End Class
View Model Classes
Public Class CarModel
Public Property Id As String
Public Property Seats As IEnumerable(Of SeatModel)
End Class
Public Class SeatModel
Public Property Manufacturer As String
Public Property Installer As String
End Class
Mapping and Test Code
<Test()> Public Sub Test()
Mapper.CreateMap(Of Car, CarModel)()
Mapper.CreateMap(Of Seat, SeatModel)() _
.ForMember("Manufacturer", Sub(cfg) cfg.Ignore()) _
.ForMember("Installer", Sub(cfg) cfg.Ignore())
Mapper.CreateMap(Of StandardSeat, SeatModel)() _
.ForMember("Installer", Sub(cfg) cfg.Ignore())
Mapper.CreateMap(Of CustomSeat, SeatModel)() _
.ForMember("Manufacturer", Sub(cfg) cfg.Ignore())
Mapper.AssertConfigurationIsValid()
Dim car As New Car With {.Id = 4}
car.Seats = New Seat() {
New StandardSeat With {.Manufacturer = "Honda"},
New CustomSeat With {.Installer = "Napa"}}
Dim model = Mapper.Map(Of Car, CarModel)(car)
model.Id.ShouldEqual("4")
model.Seats.Count().ShouldEqual(2)
' These next two assertions fail.
model.Seats.First().Manufacturer.ShouldEqual("Honda")
model.Seats.Last().Installer.ShouldEqual("Napa")
End Sub
Instead of doing this, I'd map to a parallel inheritance hierarchy on the ViewModel side. Create a SeatModel, StandardSeatModel and a CustomSeatModel. You would then use the Include() configuration option to link the Seat -> SeatModel mapping configuration to the mapping configurations to StandardSeat -> StandardSeatModel and the other.
This way, you don't need all the Ignore() s and whatnot. If you still want to flatten your original model, you'll still need to include the Include() configuration on the Seat -> SeatModel piece.
Related
I am reading up on creating class factories here: https://rubberduckvba.wordpress.com/2018/04/24/factories-parameterized-object-initialization/ and I am confused why they are making the implemented functions private, wouldn't we want them to be public so we can access them?
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Something"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private Type TSomething
Bar As Long
Ducky As String
End Type
Private this As TSomething
Implements ISomething
Public Function Create(ByVal initialBar As Long, ByVal initialDucky As String) As ISomething
With New Something
.Bar = initialBar
.Ducky = initialDucky
Set Create = .Self
End With
End Function
Public Property Get Self() As ISomething
Set Self = Me
End Property
Public Property Get Bar() As Long
Bar = this.Bar
End Property
Friend Property Let Bar(ByVal value As Long)
this.Bar = value
End Property
Public Property Get Ducky() As String
Ducky = this.Ducky
End Property
Friend Property Let Ducky(ByVal value As String)
this.Ducky = value
End Property
Private Property Get ISomething_Bar() As Long
ISomething_Bar = Bar
End Property
Private Property Get ISomething_Ducky() As String
ISomething_Ducky = Ducky
End Property
Also, why do you need to provide get and let properties for public variables in an interface?
They should be Private.
The reason is because how interfaces work in VBA: the Public members of a class module define its default interface. That means the public members of Class1 define what members Class2 must implement if it Implements Class1.
So if you make Class1_DoSomething public, then you're exposing that member on the default interface of Class2, and that's... not pretty at all.
What interface you access an object with, is determined by how you declare it.
Dim thing As Class1
Set thing = New Class1
If thing is or implements Class1, then the code after this declaration can invoke all the members exposed by the default interface of Class1 (i.e. its public members).
If Class1 implements ISomething and we declare it like this:
Dim thing As ISomething
Set thing = New Class1
Now the members we get to work with are the members defined by the public members of the ISomething class/interface.
When you implement an interface or handle events, you should never manually type the signatures; instead, pick the interface (or event provider) from the upper-left dropdown in the code pane, then pick a member from the upper-right dropdown: the VBE automatically creates the correct procedure with the correct signature, and it's always going to be a Private member - rule of thumb, anything that has an underscore in its name in VBA has no business being Public
As for why you must supply Get and Let accessors for what you defined as a public field (/variable) on an interface class... Fields are implementation details, they should never be Public in the first place. Objects expose properties, not fields - keep fields for the private internal state of the implementing class.
The reason is technical: VBA code gets compiled into a COM type library, and that library sees your public variable and says "that's going to have to be a PUT and a GET method", and the VBA code implementing that interface thus needs to implement a property for every public field, because public fields compile down to properties.
This does have interesting implications with regards to the practice of exposing a public field on a class module (breaks encapsulation vs compiles down to a property anyway!), but that is a whole other discussion.
I'm having a hard time understanding and working with Implements and I'm failing to see why this is of any use if Inheritance isn't supported with VBA.
I'm testing the code below and I keep getting the error:
Compile Error:
Object module needs to implement '~' for interface '~'
Interface: ITransmission
Option Explicit
Public pVENDOR As String
Public Property Get VENDOR() As String
End Property
Public Property Let VENDOR(ByVal value As String)
End Property
Base Class: cASN
Option Explicit
Implements ITransmission
Private Property Let ITransmission_pVENDOR(ByVal value As String)
pVENDOR = value
End Property
Private Property Get ITransmission_pVENDOR() As String
ITransmission_pVENDOR = pVENDOR
End Property
Unit Test Method: mUnitTesting
Private Sub Test_cASN()
Dim foo As cASN
Set foo = New cASN
foo.VENDOR = "Test"
End Sub
Still very new to Implements and it is something I want to learn, and I've done a fair amount of research into it.
Question 1:
Why am I getting an error message when I try to unit test this?
Question 2:
What is the real benefit here, if inheritance isn't supported?
You implement pVENDOR but not the two VENDOR properties.
I'm assuming you want the interface to be a get/let of the VENDOR property.
Your Public pVENDOR As String looks like a backing field for this property, as an Interface cannot include an implementation then its not needed.
The Interface should look like:
Public Property Get VENDOR() As String
End Property
Public Property Let VENDOR(ByVal value As String)
End Property
Then when you implement it:
Implements ITransmission
Private pVENDOR As String '// local implementation detail
Public Property Let ITransmission_VENDOR(ByVal value As String)
pVENDOR = value
End Property
Public Property Get ITransmission_VENDOR() As String
ITransmission_VENDOR = pVENDOR
End Property
And to test:
Private Sub Test_cASN()
Dim foo As cASN
Set foo = New cASN
foo.ITransmission_VENDOR = "Test"
End Sub
What is the real benefit here
How will I know when to create an interface?
The point of an Interface
Our object model contains a class called Unit and a collection of these called Units (which is stored in a Dictionary). These objects have unique Names and Keys (they originally came from a SQL db that enforced this) so I have added:
Public Units(N as String) As Unit ...
Public Units(K as Integer) As Unit...
which return a Unit object from the Units collection.
In Excel VBA, one can refer to most objects using similar methods; Worksheets(1) returns the first sheet, while Worksheets("Bob") returns the named sheet. But they have one additional method, Worksheets, which returns the entire collection. It's as if they have this method...
Public Worksheets() As List(Of Worksheet)
But you can't use List in interop (right?) so it's more like...
Public Worksheets() As ArrayList
So how would I do the same basic API in .net with interop? That is, have three methods...
Public Units(N as String) As Unit ...
Public Units(K as Integer) As Unit...
Public Units() As ArrayList...
As I understand it only the first method of a given name is exported (is this correct?). So how does Excel do it, and can I fake that in .net?
VBA's Worksheets is not a method. It is a class, Worksheets, that has a default property Item that accepts a parameter of type Variant. There is no overloading (COM does not support it), it's just that Variant can hold both a number or a string.
If you want a similar structure in VB.NET, you can have a collection class that implements a default property as VB.NET understands it, and this time you can overload it.
Public Class UnitsCollection
Default Public ReadOnly Property Item(ByVal i As Integer) As Unit
Get
Return ...
End Get
End Property
Default Public ReadOnly Property Item(ByVal i As String) As Unit
Get
Return ...
End Get
End Property
End Class
basically I cant find in google some similar problems by just pasting the root exception "#NaturalId only valid on root entity (or its #MappedSuperclasses)" in the search tab. I'm using a Joined, Multiple Table inheritance strategy to map my concrete/children Entities(Student,Employee) including their Abstract Parent (Person) to the three tables in my database, so far I never had a problem, Until I realize I need to implement a custom query for my Student Entity, using the Student's studentId. I manage to extract the underlying Hibernate-session from the Entity-manager and now I can clearly see and use the methods I need from my beloved HibernateSession, (we all know Hibernate has methods for naturalIds such as (byId, byNaturalId etc..)), and these kinds of methods are really really REALLY useful for querying entities. So I just annotate my studentId data member with #NaturalId until.. I performed some operation (save/create) and then multiple lines of exception are thrown to me. And the root cause is..
org.hibernate.AnnotationException: #NaturalId only valid on root entity (or its #MappedSuperclasses)
I will paste the codes of my entities for added information
The Parent abstract base class
Person class
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="PERSON_TYPE")
#Table(name="PERSON")
public abstract class Person {
#Id
#GeneratedValue
#Column(name="person_ent_id", insertable = false)
private int personId;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "age")
private int age;
#Column(name = "postal_id")
private String postalId;
.. getter and setter methods declarations
The Entity sub-class
Student class
#Entity
#Table(name = "STUDENT")
#DiscriminatorValue("S")
#PrimaryKeyJoinColumn(name = "student_ent_id", referencedColumnName = "person_ent_id")
public class Student extends Person {
#Column (name = "student_ent_id", insertable=false, updatable=false)
private int studentEntId;
#NaturalId
#Column(name = "school_id")
private String schoolId;
#Column(name = "school_name")
private String shoolName;
public Student() {
super();
}
...getter and setter methods declarations
Questions:
Is there a anything I can do to make a unique query for different student entities? because Hibernate's naturalId methods are really useful to perform operations with entities such as get or
update
Is there any work-around to accomplish what I want without sacrificing my Design with the Entities and the database table? I want the studentId to act as a naturalId IF it would be possible. Any suggestions/help/comments please. Any thing will be greatly appreciated.
No, unfortunately this is not possible. I think it's because you can make NaturalId queries only on the whole table, so it needs to be on a root Entity. But if you only want to make sure that schoolId is unique for all Students you can use #Column(name = "school_id", unique = true).
How would it work in Subsonic's SimpleReporitory if I wanted to be able to have a 1 to many relationship between objects?
Would I have to create a bridge object and then build my parent object at runtime, or is this support built in?
What I am looking for is the folowing:
Adam's Example Shop...
Public Class Shop
Private m_id As Integer
Private m_Name As String
Private m_Employees As List(Of Employee)
Public Property Id() As Integer
Get
Return m_id
End Get
Set(ByVal value As Integer)
m_id = value
End Set
End Property
Public Property Name() As String
Get
Return m_Name
End Get
Set(ByVal value As String)
m_Name = value
End Set
End Property
Public Property Employees() As List(Of Employee)
Get
Return m_Employees
End Get
Set(ByVal value As List(Of Employee))
m_Employees = value
End Set
End Property
End Class
Public Class Employee
Private m_id As Integer
Private m_Name As String
Public Property Id() As Integer
Get
Return m_id
End Get
Set(ByVal value As Integer)
m_id = value
End Set
End Property
Public Property Name() As String
Get
Return m_Name
End Get
Set(ByVal value As String)
m_Name = value
End Set
End Property
End Class
Main bits:
Dim repo As New SimpleRepository("SubSonicObjectTest", SimpleRepositoryOptions.RunMigrations)
Dim emplyee1 As New Employee
emplyee1.Name = "Martin"
Dim emplyee2 As New Employee
emplyee2.Name = "Adam"
Dim shop As New Shop
shop.Name = "Sub Sonic Store"
shop.Employees = New List(Of Employee)
shop.Employees.Add(emplyee1)
shop.Employees.Add(emplyee2)
repo.Add(Of Shop)(shop)
I think this should create 3 tables:
Shops
Employees
ShopsToEmployees (or some other naming convention)
But I only get a Channels table!
I'm updating the SimpleRepo stuff currently to automatically create joined tables based on collections. Not easy to determine many/many vs 1/many - but I have some ideas :).
To create a one to many relationship you just have to create the object model, SubSonic should do the rest for you e.g.
public class Shop
{
public int Id { get; set; }
public String Name { get; set; }
public List<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public String Name { get; set; }
}
EDIT: This should generate two tables when you run a migration not 3. The 3 three tables you describe in your question would represent a many to many relationship. Also in your example you're not saving your Employees, SubSonic does not cascade saves so you'll need to save your Shop then add the Employees to it and BatchSave the Employees.