I am testing N:M relations. I am testing in a testprogram and in a program skeleton.
In the testprogram things work fine. I am using this model where TExamQuestion is the intermediary class.
[Entity]
[Automapping]
TQuestion = class
private
fID: integer;
fText: string;
fExamQuestions: TList<TExamQuestion>;
public
property ID: integer read fID write fID;
property Text: string read fText write fText;
[ManyValuedAssociation([], CascadeTypeAll, 'fQuestion')]
property ExamQuestions: TList<TExamQuestion> read fExamQuestions write fExamQuestions; // Used in TFormMain.DisplayView1
end; // TQuestion
[Entity]
[Automapping]
TExam = class
private
fID: integer;
fText: string;
fExamQuestions: TList<TExamQuestion>;
public
property ID: integer read fID write fID;
property Text: string read fText write fText;
[ManyValuedAssociation([], CascadeTypeAll, 'fExam')]
property ExamQuestions: TList<TExamQuestion> read fExamQuestions write fExamQuestions; // Used in TFormMain.DisplayView1
end; // TExam
[Entity]
[Automapping]
TExamQuestion = class
private
fID: integer;
// Link to TExam
[Association([TAssociationProp.Lazy], [])]
[JoinColumn('ID_QUESTION', [])]
fQuestion: Proxy<TQuestion>;
// Link to TExam
[Association([TAssociationProp.Lazy], [])]
[JoinColumn('ID_EXAM', [])]
fExam: Proxy<TExam>;
function GetExam: TExam;
procedure SetExam(const Value: TExam);
function GetQuestion: TQuestion;
procedure SetQuestion(const Value: TQuestion);
public
property ID: integer read fID write fID;
property Question: TQuestion read GetQuestion write SetQuestion;
property Exam: TExam read GetExam write SetExam;
end; // TExamQuestion
In the program skeleton I am using this (partial) model with TExamPage as intermediary class:
[Entity]
[Automapping]
[Table('CustomPages')] // Rename the table
[Id('fID', TIdGenerator.IdentityOrSequence)] // Set the way the fID is generated
TCustomPage = class( TPersistent )
private
[Column('ID', [TColumnProp.Required])] // Rename this column
fID: integer;
fGUID: TGUID;
fDeleted: boolean;
fExamPages: TList<TExamPage>;
// Link TCustomPage to TProduction
[Association([TAssociationProp.Lazy], [])]
[JoinColumn('ID_PRODUCTION', [])] // Set the fieldname to ID_PRODUCTION
fProduction: Proxy<TProduction>;
function GetProduction: TProduction;
procedure SetProduction(const Value: TProduction);
public
constructor Create;
property ID: integer read fID write fID;
property GUID: TGUID read fGUID write fGUID;
property Deleted: boolean read fDeleted write fDeleted;
// Relations
property Production: TProduction read GetProduction write SetProduction; // Belongs to production
// Make the ManyToOne relation with the ExamPage
[ManyValuedAssociation([], CascadeTypeAll, 'fPage')] // Must refer to the property fPage in TExamPage
property Exams: TList<TExamPage> read fExamPages write fExamPages; // Has zero or more pages
end; // TCustomPage
[Entity]
[Automapping]
[Table('CustomExams')] // Rename the table
[Id('fID', TIdGenerator.IdentityOrSequence)] // Set the way the fID is generated
TCustomExam = class( TPersistent )
private
[Column('ID', [TColumnProp.Required])] // Rename this column
fID: integer;
fGUID: TGUID;
fDeleted: boolean;
fExamPages: TList<TExamPage>;
// Link TCustomPage to TProduction
[Association([TAssociationProp.Lazy], [])]
[JoinColumn('ID_PRODUCTION', [])] // Set the fieldname to ID_PRODUCTION
fProduction: Proxy<TProduction>;
function GetProduction: TProduction;
procedure SetProduction(const Value: TProduction);
public
constructor Create;
property ID: integer read fID write fID;
property GUID: TGUID read fGUID write fGUID;
property Deleted: boolean read fDeleted write fDeleted;
// Relations
property Production: TProduction read GetProduction write SetProduction; // Belongs to production
// Make the ManyToOne relation with the ExamPage
[ManyValuedAssociation([], CascadeTypeAll, 'fExam')] // Must refer to the property fExam in TExamPage
property ExamPages: TList<TExamPage> read fExamPages write fExamPages;
end; // TCustomExam
[Entity]
[Automapping]
[Table('ExamPage')] // Rename the table
[Id('fID', TIdGenerator.IdentityOrSequence)] // Set the way the fID is generated
// This is a helper class to create a N:M relation between TCustomExam and TCustomPage
TExamPage = class( TPersistent )
private
[Column('ID', [TColumnProp.Required])] // Rename this column
fID: integer;
fGUID: TGUID;
fDeleted: boolean;
// Link to TCustomExam
[Association([TAssociationProp.Lazy], [])]
[JoinColumn('ID_EXAM', [])]
fExam: Proxy<TCustomExam>;
// Link to TCustomPage
[Association([TAssociationProp.Lazy], [])]
[JoinColumn('ID_PAGE', [])]
fPage: Proxy<TCustomPage>;
function GetExam: TCustomExam;
function GetPage: TCustomPage;
procedure SetExam(const Value: TCustomExam);
procedure SetPage(const Value: TCustomPage);
public
constructor Create;
property ID: integer read fID write fID;
property GUID: TGUID read fGUID write fGUID;
property Deleted: boolean read fDeleted write fDeleted;
// Relations
property Exam: TCustomExam read GetExam write SetExam;
property Page: TCustomPage read GetPage write SetPage;
end; // TExamPage
ISSUE:
I want to display the TCustomPages related to one TCustomExam. When accessing TCustomExam.ExamPages[x].Page it turns out that Page = nil. In the debugger the proxy shows as loaded and all necessary records are available in the database.
If I do the same (display TQuestions related to one TExam) in the test program, the TExam.ExamQuestions[x].Question is assigned.
OBSERVATIONS:
Looking at the properties at runtime of the testprogram: it turns out that the proxy TExamQuestion.Question has a controller assigned (TObjectManager.TAssociationProxyController as IProxyController). Also the Loaded property is TRUE.
Looking at the properties at runtime of the program skeleton it turns out that the proxy TExamPage.Page has no controller assigned. But the loaded property is TRUE.
QUESTION
a. Would the missing controller be the cause of the program skeleton not displaying the TCustomPage instances belonging to one TCustomExam ?
b. If so, what are possible causes for the missing controller ?
a. Probably
b. How are you retrieving the records? There is a maximum level of object tree that is loaded from the database, if you are retrieving a too big tree where ExamPage or Page is higher than 5th level it might come with nil. Other than this I see no reason, unless your GetPage code is not correct.
The records are retrieved through:
TPublisher > TProduction > TCustomExam > TExamPage > TCustomPage and simultaneously
TPublisher > TProduction > TCustomPage
If I retrieve the records through:
TProduction > TCustomExam > TExamPage > TCustomPage and simultaneously
TProduction > TCustomPage
then the pages show. So exceeding the 5th level seems to be the case. I will change the way the records are retrieved.
a. Can you add a chapter on the limits to the manual?
Thanx, Wagner.
You can configure the maximum level loaded by using MaxEagerFetch:
But the best way to solve this is implementing ExamPages property (more specifically FExamPages field) as Proxy. This will make sure it will be loaded at any level, and actually it doesn't make much sense to leave such lists as eager loading.
Using Proxy<> all the way did the trick Wagner. Thanx!