问题
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.
回答1:
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.
回答2:
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!
回答3:
@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.
来源:https://stackoverflow.com/questions/18011242/unmapped-property-on-the-angular-breeze-spa-template