Save changes to entities in cache by calling the EntityManager’s saveChanges
method. This topic delves more deeply into the save process and how you control it.
This page is not ready for publication. It will cover:
hasChanges
and getChanges
rejectChanges
saveResult.entities
collection.saveResult.keyMappings
.DataProperty.concurrencyMode
SaveOptions
By default the EntityManager.saveChanges
method sends a save request to a server endpoint called ‘SaveChanges’.
But you might have a specific business process to perform when you save a certain constellation of entities. Perhaps the actual storing of changes in the database is only a part of a much larger server-side workflow. What you really have is a ‘command’ that includes a database update.
You could route this command through a single ‘SaveChanges’ endpoint and let the corresponding server method dispatch the save request to the appropriate command handler. That could get messy. It can make more sense to POST requests to command-specific endpoints, passing along just the right entity set in the request body.
That’s what the ‘Named Save’ is for. With a ‘Named Save’, you can re-target a ‘save’ to a custom server endpoint such as an arbitrarily named action method on a separate, dedicated Web API controller.
You still call EntityManager.saveChanges
but you pass in a SaveOptions
object that specifies the resourceName
to handle the request. The server should route the request to a suitable controller action method. You’d also set the SaveOptions.dataService
if you need also to target a different controller.
Assuming that you want to save all pending changes to a custom endpoint, you could write:
var so = new SaveOptions({ resourceName: 'myCustomSave' });
// null = 'all-pending-changes'; saveOptions is the 2nd parameter
myEntityManager.SaveChanges(null, so );
You are more likely to assemble a list of entities to save to that endpoint … a list consistent with the semantics of ‘MyCustomSave’ in which case you’d probably pass that list in the ‘saveChanges’ call:
myEntityManager.SaveChanges(selectedEntities, so );
The Breeze client still sends a JSON change-set bundle to ‘MyCustomSave’ as it would with a normal saveChanges
call. The POST method on the server that handles the ‘MyCustomSave’ endpoint should have the same as signature as the ‘SaveChanges’ method.
Here is what a typical JSON payload looks like when SaveChanges is called (when using the default (web api) DataServiceAdapter):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
{
"entities": [
{
"OrderID": -1,
"CustomerID": "785efa04-cbf2-4dd7-a7de-083ee17b6ad2",
"EmployeeID": 1,
"OrderDate": null,
"ShippedDate": null,
"Freight": null,
"RowVersion": 0,
"entityAspect": {
"entityTypeName": "Order:#Foo",
"defaultResourceName": "Orders",
"entityState": "Added",
"originalValuesMap": {
},
"autoGeneratedKey": {
"propertyName": "OrderID",
"autoGeneratedKeyType": "Identity"
}
}
},
{
"OrderID": -1,
"ProductID": 1,
"UnitPrice": 0,
"Quantity": 5,
"Discount": 0,
"RowVersion": 0,
"entityAspect": {
"entityTypeName": "OrderDetail:#Foo",
"defaultResourceName": "OrderDetails",
"entityState": "Added",
"originalValuesMap": {
},
"autoGeneratedKey": null
}
}
],
"saveOptions": {
}
}
A few things to notice:
entityAspect
property, which identifies its entityTypeName
and entityState
. These are used to create the proper type on the server, and determine how to update the persistence layerAdded
, and the OrderID property has a temporary value that will be replaced by the server-generated id.originalValuesMap
contains the original values of each property that was changed on the client. This provides a clue to the server about which server-side properties need to be updated.autoGeneratedKey
property identifies the key generation policy of the entity according to the metadata. It provides a hint to the server about whether the client expects the server to provide a generated key if the entity is Added.saveOptions
property is an object that can contain anything the client wants to send. This could be a clue to the server for special types of savesHere is what a typical JSON response looks like when SaveChanges is called (when using a web api server):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{
"$id": "1",
"$type": "Breeze.ContextProvider.SaveResult, Breeze.ContextProvider",
"Entities": [
{
"$id": "2",
"$type": "Foo.Order, Model_NorthwindIB_CF.EF6",
"OrderID": 212008,
"CustomerID": "785efa04-cbf2-4dd7-a7de-083ee17b6ad2",
"EmployeeID": 1,
"OrderDate": null,
"RequiredDate": null,
"ShippedDate": null,
"Freight": null,
"ShipName": "Test 2016-07-08T17:50:10.982Z",
"RowVersion": 0,
"Customer": null,
"Employee": null,
"OrderDetails": [
{
"$id": "3",
"$type": "Foo.OrderDetail, Model_NorthwindIB_CF.EF6",
"OrderID": 212008,
"ProductID": 1,
"UnitPrice": 0,
"Quantity": 5,
"Discount": 0,
"RowVersion": 0,
"Order": {
"$ref": "2"
},
"Product": null
}
],
"InternationalOrder": null
},
{
"$ref": "3"
}
],
"KeyMappings": [
{
"$id": "5",
"$type": "Breeze.ContextProvider.KeyMapping, Breeze.ContextProvider",
"EntityTypeName": "Foo.Order",
"TempValue": -1,
"RealValue": 212008
}
],
"DeletedKeys": [
{
"$id": "6",
"$type": "Breeze.ContextProvider.EntityKey, Breeze.ContextProvider",
"EntityTypeName": "Foo.Supplier",
"KeyValue": [
1210
]
}
],
"Errors": null
}
Here we notice:
$id
and $ref
properties are used to identify the relationships between the entities in this payload only. The real entity relationship is established (on server and client) by the OrderID.OrderID
value has been replaced by the server-generated ID. The KeyMappings
property identifies the keys that were replaced on the server. This tells the Breeze client how to update the entities in its cache. It will find the Order entity with OrderID -1, and change its OrderID to 212008, and change the foreign key in all related entities.DeletedKeys
property contains the EntityKey values for entities that were deleted on the server but not the client. This tells the Breeze client to remove these entities from the cache.Errors
property