UnMapped property on the Angular/Breeze SPA template
Asked Answered
R

3

0

I am using the Angular/Breeze SPA template in visual studio 2012. I have added an unmapped property in the TodoList server model.

[NotMapped]
public string MyUnmappedProperty{ get{return "testString";}} 

I have registered the unmapped property in the constructor on the client model, specifically in the todo.model.js like Ward suggested here : Handling calculated properties with breezejs and web api

function TodoList() {
        this.title = "My todos"; // defaults
        this.userId = "to be replaced";
         this.myUnmappedProperty="";
    }

When the getTodos() method is called on the todo.controller.js for the first time, the "myUnmappedProperty" has the value of the empty string as set in the constructor. Only after getTodos() is called for the second time( I have added a button that does that) with 'forceRefresh' set to true (getTodos(true)) in order to query the server, only then I see the myUnmappedProperty getting the value "testString".

I am wondering why I am getting this behaviour. Is it possible to have the unmapped property filled with the server value the first time the getTodos() is called?

Thank you.

Rawdan answered 2/8, 2013 at 7:31 Comment(0)
T
2

Did you find a solution to this problem? I have been struggling with the same problem and just recently found this SO post by CassidyK, in which he solves a very similar problem by changing a single line in the Breeze source code (breeze.debug.js). Here is the code snippet he changed (taken from CassidyK's post):

proto.initializeFrom = function (rawEntity) {
    // HACK:
    // copy unmapped properties from newly created client entity to the rawEntity.
    // This is so that we don't lose them when we update from the rawEntity to the target.
    // Something that will occur immediately after this method completes. 
    var that = this;
    this.entityType.unmappedProperties.forEach(function(prop) {
        var propName = prop.name;
        that[propName] = rawEntity[propName];  // CassidyK 
        //rawEntity[propName] = that[propName]; // Breeze 
    });

    if (!this._backingStore) {
        this._backingStore = { };
    }
};

I have tried this solution and it seems to work just fine. Notice that this change will probably get you into trouble if you ever have a case in which you have a server side property that's not mapped in the database, and need the client side to overwrite this value when the object is created at the client side.

Tremulous answered 7/11, 2013 at 9:18 Comment(2)
Thank you Larrifax. CassidyK wrote a comment in my post some weeks ago mentioning his solution but he deleted it afterwards. I didn't try his solution because a changed the logic in my project not to use unmapped properties. Which version of breezeJS are you using?Rawdan
@KostasValais I'm using Breeze 1.4.5, which I have modified according to the code snippet in my post above.Tremulous
U
1

Some background for the reader.

It's important to differentiate

  • "unmapped" from the EF perspective
  • "not serialized" from the Json.Net perspective
  • "unmapped" from the Breeze perspective

[NotMapped] is an EF attribute that tells EF "this property is not mapped to the database".

[JsonIgnore] is a Json.Net attribute that tells JSON.Net "don't serialize this property when you send it to the client"

Only metadata can tell Breeze which properties are "mapped" ... from its perspective.

Breeze generally ignores incoming properties that aren'd defined in metadata. However, if a non-metadata property that is defined in the constructor, Breeze adds it to metadata and classifies it as "unmapped".

For Breeze that means "serialize this property, notify the UI when it changes, but this is not part of the entity's data model and should not be change-tracked (i.e., changes to it do not affect the EntityState)."

Let's put these ideas together to understand your question.

You told EF not to map the MyUnmappedProperty property. You're probably using the EFContextProvider to generate the metadata so that property isn't in the metadata either.

But Json.Net doesn't know about any of this. Therefore, whenever it serializes an instance of your TodoList, it sends the MyUnmappedProperty property ("testString") to the Breeze client.

At first Breeze won't do anything with it. If the server sends this property with any value, Breeze will ignore it.

But you added the MyUnmappedProperty property to the TodoList type constructor so now Breeze will recognize this property as "unmapped" and will materialize its value if it appears in the JSON query results.

When you create a new instance of TodoList (with new or entityManager.CreateEntity(...)), then MyUnmappedProperty property is set to '' per the ctor.

When you query for TodoList, the Json.NET sends {MyUnmappedProperty: "testString} in the JSON results. The Breeze client recognizes the MyUnmappedProperty, and accordingly sets that property on the materialized TodoList entity instance.

That's what is supposed to happen.

Can you demonstrate that a queried TodoList is ever materialized such that its MyUnmappedProperty is not "testString"?

You've checked the network traffic and you see that {MyUnmappedProperty: "testString} is actually coming over the wire but is not materialized? And you say that it is materialized in a second query?.

I need a repro of that!

Uncommitted answered 3/8, 2013 at 21:0 Comment(0)
R
0

@Ward, Thank you for your answer.

First of all I forgot to mention that I am using Breeze version 1.4.0.

You've checked the network traffic and you see that {MyUnmappedProperty: "testString} is actually coming over the wire : Yes I have checked it and it is include in the JSON send from the server.

3 small additions are needed to the spa template to get the behavior I am experiencing.

In the todo.view.html I add the button "myGetBtn" and a span "mySpan" with an angular binding :

...
<button data-ng-click="addTodoList()">Add Todo list</button>
<br/>
<p>
    <button id="myGetBtn" ng-click="getTodos(true)">Get todos</button>
</p>
<article class="todoList" data-ng-repeat="list in todoLists">
    <header>
        <form data-ng-submit="endEdit(list)">
            <span id="mySpan">myNotMappedProperty: {{list.MyNotMappedProperty}}</span>
            <input  data-ng-model="list.title" 
                    data-selected-when="list.isEditingListTitle"
                    data-ng-click="clearErrorMessage(list)" 
                    data-on-blur="endEdit(list)"/>           
        </form>
    </header>
...

In the .Net POCO class TodoList I add

[NotMapped]
public string MyNotMappedProperty { get { return "testString"; } }

In the todo.model.js I modify the constructor like this:

function TodoList() {
        this.title = "My todos"; // defaults
        this.userId = "to be replaced";
        this.MyNotMappedProperty = "";
    }

Now if you run the app and successfully login the {{list.MyNotMappedProperty}} shows the empty string, if you then press the "Get Todos" button the {{list.MyNotMappedProperty}} shows the value "testString" .

In both cases the query is the same and the Json returned is the following:

[{"$id":"1","$type":"TodoTest.Models.TodoList, TodoTest","TodoListId":5,"UserId":"kostas","Title":"My todos","MyNotMappedProperty":"testString","Todos":[{"$id":"2","$type":"TodoTest.Models.TodoItem, TodoTest","TodoItemId":4,"Title":"dasdas","IsDone":false,"TodoListId":5,"TodoList":{"$ref":"1"}}]}]

I would like to get the "testString" shown in the first place without pressing the button I added.

I don't know if the info I provided is sufficient to reproduce the behavior so please tell me if you need any additional info.

Thank you.

Rawdan answered 6/8, 2013 at 12:43 Comment(4)
Isn't your constructor setting whatever value is returned from the server to "" on construction? Maybe I am reading it wrong but if you looked at the json payload it is almost certainly "test string" and then your constructor is clearing it the first time, but since you are refreshing an already constructed entity it is "test string" second timeBrooksbrookshire
@PW Kad, Probably this is what is happening. Is there a way to pass the "testString" value in the first place without refreshing?Rawdan
I don't fully understand exactly what you are trying to do but if at some point this.MyUnmappedProperty has a value and you are setting it to "" then just check if it has a value before you do change it.Brooksbrookshire
I want to extend the Entity (TodoList) client side definition so that it can show the value returned by server-side calculated property "MyNotmappedProperty" when querying the database. Pretty much what Ward does with the Fullname here #16524573Rawdan

© 2022 - 2024 — McMap. All rights reserved.