Reputation: 1541
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): </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
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