TheMook
TheMook

Reputation: 1541

Breeze child entities within observablearray not being created as "dependentobservable"

Hmm. Seems wrong to me. I have a breeze query that does this:

             var query = new breeze.EntityQuery()
                     .from("AllGames")
                     .expand("Sets, MVP, TeamerMVP.Person")
                     .orderBy("GameDateTime desc");

(There can be none or many TeamerMVP records per game and there is a one to one relationship with the person table, hence the expand statement for TeamerMVP.Person so I can directly access the "Name" property in person. This works perfectly in the directly one-to-one related MVP (of which there can only be one per game))

and all seems fine - the related entities are loaded and I can access the child entities in prototype functions alright as well. However I get problems trying to do much within the html bindings with the child entities and, on checking in chrome's console, it appears it may be because the child entities, unlike the parent, are not declared as dependentobservable, they are just plain "objects".

viewmodel defines parent as:           games = ko.observableArray([])

Evaluation in console:

$data
    Object
        games: Object[0]
        _latestValue: Array[7]
        0: Game
        1: Game
        2: Game
            >GameDateTime: function dependentObservable() {
            >HomeGame: function dependentObservable() {
            >ID: function dependentObservable() {
            >LeagueGame: function dependentObservable() {
            >MVP: function dependentObservable() {
            >MVP_PersonID: function dependentObservable() {
            >NLorRL: "<strong>NL</strong> (A)"
            >Opponent: function dependentObservable() {
            >Sets: Object[0]
            >TeamerMVP: Object[0]

In the expanded node you can see that the parent entity-level properties (GameDateTime, HomeGame, etc.) are created as dependentobservables but the child entities (Sets, TeamerMVP) are not and I'm guessing this is why I'm having some trouble in the html template bindings doing things like "foreach" as knockout simply doesn't seem to see those child entities.

Is there something I'm missing? Should I be creating a "games.Sets = ko.observableArray([])" declaration in the viewmodel to act as a container for the child entity when it's created?

The json data returned shows the following:

TeamerMVP: [{$id:20, $type:lbD.model.TeamerMVP, lbD.model, GameID:3, PersonID:4,…}]
    0: {$id:20, $type:lbD.model.TeamerMVP, lbD.model, GameID:3, PersonID:4,…}
    $id: "20"
    $type: "lbD.model.TeamerMVP, lbD.model"
    GameID: 3
    Person: {$id:21, $type:lbD.model.Person, lbD.model, ID:4, Name:Mike Connor, isAdmin:false,…}
    PersonID: 4

and it's easy to see the "Name" property within the related Person table.

Edited to add: It's just the "TeamerMVP" relationship that is troublesome. Just managed to do something with a "forEach" on the "Sets" entity so that's working fine, even though it's not a dependentobservable.... I'm puzzled.

Further edited for Ward's question:

At breakpoint as requested I see these listed:

breezeConfig.manager.metadataStore._structuralTypeMap
Object
  >Game:#lbD.model: ctor
  >GameSet:#lbD.model: ctor
  >Person:#lbD.model: ctor
  >TeamerMVP:#lbD.model: ctor
  >Venue:#lbD.model: ctor
  >__proto__: Object

These are the concrete table names from my (simple) database. If I expand "GameSet" then one of the properties is: defaultResourceName: "Sets" which is the navigation property name (defined in model as "Public Overridable Property Sets() As ICollection(Of GameSet)").

Ah... just expanded "TeamerMVP" and its defaultresourcename is "TeamerMVPs" which is a small but subtle difference! I don't recall defining a version with the "s" on the end anywhere and a quick search of the project reveals no results. Curious. Maybe that's the issue, I'll have a quick play with the html bindings now.

Edit 3: HTML Bindings info

Nope, not playing ball at all...

I have this template:

 <script type="text/html" id="TeamerMVPTemplate">
    <div>Teamer MVP(s):&nbsp;</div>
    <li data-bind="text: Person().Name"></li>
</script>

Which is embedded in another template (the main "foreach: games" one) which is where it is referenced:

<span class="fixtureBoxLine" data-bind="template: { name: 'setScoresTemplate', foreach: Sets }"></span>
<span class="fixtureBoxLine" data-bind="template: { name: 'TeamerMVPTemplate', foreach: TeamerMVPs }"></span>

...and it just silently breaks. No error message but the processing stops on the first loop of the first game. The "foreach: Sets" template immediately above it works perfectly.

If I change "foreach: TeamerMVPs" back to "foreach: TeamerMVP" in the binding declaration above, all games are processed but nothing is shown in the template for TeamerMVP so I guess "TeamerMVPs" is the correct entity reference and there's something odd going on with the way I'm trying to access it. Is "Person().Name" the correct binding in the template?

===========================================

Edit 4: Clarification of model

manager.metadataStore.getEntityType('Game').navigationProperties returns 4 navigation properties. This is correct, there should be a collection of 0 to many "Sets", a collection of 0 to many "TeamerMVP", a 0 to one "MVP" and a 0 to one "Venue". All are present.

There can be only one (or none) MVP but it is entirely correct for there to be 0 to many TeamerMVP. This relationship is set up in exactly the same way as the "Sets" relationship that works. The only slight difference being that the "TeamerMVP" tavle itself has a 1-to-1 relationship with the "Person" table as any TeamerMVP must be a valid person. This is why my breeze query defines an "expand" that refers to "TeamerMVP.Person" and this appears to work as the json data returned is exactly as I would expect. In the raw json, the navigation property is referred to as "TeamerMVP" and it is only in the "structuraltypemap" that the defaultResourceName is referred to as "TeamerMVPs".

If I leave the html binding as "forEach TeamerMVP" then all games are returned and bound to the "foreach games" parent template as expected, but no data are bound to the TeamerMVP template and no errors are shown in the console. If I change the binding to "foreach TeamerMVPs" then only one game is returned and then processing stops at the point that the binding would occur and data would be shown. No error is show in the console, however.

It appears as if breeze is creating the navigation property as "TeamerMVPs" internally and yet the json is returning "TeamerMVP" and maybe that disparity is why the binding appears to work and yet no data are bound? It's way above my comprehension at present!

=================================================================

Edit 5: Add model information

Game Model

Public Class Game
    Public Property ID() As Integer
    Public Property GameDateTime() As System.DateTime
    Public Property Opponent() As String
    Public Property HomeGame() As Boolean
    Public Property LeagueGame() As Boolean
    Public Property MVP_PersonID() As Nullable(Of Integer)
    Public Property VenueID() As Nullable(Of Integer)
    Public Property isNL() As Boolean

    Public Overridable Property Sets() As ICollection(Of GameSet)
    Public Overridable Property MVP() As Person
    Public Overridable Property TeamerMVP() As ICollection(Of TeamerMVP)
    Public Overridable Property Venue() As Venue
End Class

TeamerMVP Model

Public Class TeamerMVP
    Public Property GameID() As Integer
    Public Property PersonID() As Integer

    Public Overridable Property Person() As Person
End Class

Person Model

Public Class Person
    Public Property ID() As Integer
    Public Property Name() As String
    Public Property isAdmin() As Boolean
    Public Property email() As String
    Public Property type() As String
    Public Property Image() As String
    Public Property thumbImage() As String
    Public Property backImage1() As String
    Public Property backImage2 As String
    Public Property Height() As String
    Public Property YearStarted() As String
    Public Property Position() As String
    Public Property PreviousClubs() As String
    Public Property ShirtNumber() As String
    Public Property isNL() As Boolean

    Public Overridable Property Games_MVP() As ICollection(Of Game)
    Public Overridable Property Games_TeamerMVP() As ICollection(Of Game)
End Class

GameSet Model

Public Class GameSet
    Public Property ID() As Integer
    Public Property GameID() As Integer
    Public Property SetNo() As Integer
    Public Property ourScore() As Integer
    Public Property theirScore() As Integer

    Public Overridable Property Game() As Game
End Class

Using breezeConfig.manager.getEntities('TeamerMVP') in the console at the success breakpoint does indeed reveal a whole load of "TeamerMVP" entities. The network resource tab shows the correct related entities for "TeamerMVP" in the raw returned data too.

However, running data.results[0].TeamerMVP() in the console at the success breakpoint still returns nothing:

data.results[0].TeamerMVP()
>[]
data.results[2].TeamerMVP()
>[]

The first one above is correct, the first returned game has no associated "TeamerMVP" entities as yet, but the 3rd game in the sequence does have associated entitities as shown in the raw json data returned:

TeamerMVP: [{$id:4, $type:lbD.model.TeamerMVP, lbD.model, GameID:24, PersonID:14,…},…]
 >0: {$id:4, $type:lbD.model.TeamerMVP, lbD.model, GameID:24, PersonID:14,…}
 >1: {$id:6, $type:lbD.model.TeamerMVP, lbD.model, GameID:24, PersonID:15,…}

Upvotes: 0

Views: 1809

Answers (1)

TheMook
TheMook

Reputation: 1541

Final Update

Wow, what can I say but huge thanks to Ward from Ideablade for devoting so much time to helping solve this problem. It turned out, in the end, to be the simplest of things: a missing return navigation property from the dependent "TeamerMVP" to the parent "Game".

So all I had to do was change the TeamerMVP model from:

Public Class TeamerMVP
    Public Property GameID() As Integer
    Public Property PersonID() As Integer

    Public Overridable Property Person() As Person
End Class

To:

Public Class TeamerMVP
    Public Property GameID() As Integer
    Public Property PersonID() As Integer

    Public Overridable Property Game() As Game
    Public Overridable Property Person() As Person
End Class

...and all immediately came to life with the TeamerMVP entities appearing where they should.

Ward and his colleagues at Ideablade are looking into any possibilities of highlighting/trapping this situation if possible. If you're reading this question as a beginner/trying to find out more about Breeze then I can only encourage you to try it out in full as it's an amazing product and I've only just scratched the surface of what it can do so far. Most importantly it has a great team behind it. Thanks Ward!

Upvotes: 1

Related Questions