Many-to-many: missing IProxyController ?

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:


http://www.tmssoftware.biz/business/aurelius/doc/web/index.html?global_configuration.htm

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!