I\'m attempting to use the SaveBundleToSaveMap snippet linked below to implement custom save handling on the server side of a breeze web api implementation.
SaveBund
I looked into this. You don't actually need to make a change to the Breeze code to accomplish what you want. The ContextProvider
is designed such that you can do just about whatever you want during save.
I'm curious: what "custom save handling" do you want to perform that you can't do today with the BeforeSave and AfterSave logic? I see in your "stopgap" code that you're calling BeforeSave
on the SaveWorkState
. What more do you need?
As an exercise, I wrote a NorthwindIBDoNotSaveContext that does what you want. Here's how it goes:
/// <summary>
/// A context whose SaveChanges method does not save
/// but it will prepare its <see cref="SaveWorkState"/> (with SaveMap)
/// so developers can do what they please with the same information.
/// See the <see cref="GetSaveMapFromSaveBundle"/> method;
/// </summary>
public class NorthwindIBDoNotSaveContext : EFContextProvider<NorthwindIBContext_CF>
{
/// <summary>
/// Open whatever is the "connection" to the "database" where you store entity data.
/// This implementation does nothing.
/// </summary>
protected override void OpenDbConnection(){}
/// <summary>
/// Perform your custom save to wherever you store entity data.
/// This implementation does nothing.
/// </summary>
protected override void SaveChangesCore(SaveWorkState saveWorkState) {}
/// <summary>
/// Return the SaveMap that Breeze prepares
/// while performing <see cref="ContextProvider.SaveChanges"/>.
/// </summary>
/// <remarks>
/// Calls SaveChanges which internally creates a <see cref="SaveWorkState"/>
/// from the <see param="saveBundle"/> and then runs the BeforeSave and AfterSave logic (if any).
/// <para>
/// While this works, it is hacky if all you want is the SaveMap.
/// The real purpose of this context is to demonstrate how to
/// pare down a ContextProvider, benefit from the breeze save pre/post processing,
/// and then do your own save inside the <see cref="SaveChangesCore"/>.
/// </para>
/// </remarks>
/// <returns>
/// Returns the <see cref="SaveWorkState.SaveMap"/>.
/// </returns>
public Dictionary<Type, List<EntityInfo>> GetSaveMapFromSaveBundle(JObject saveBundle)
{
SaveChanges(saveBundle); // creates the SaveWorkState and SaveMap as a side-effect
return SaveWorkState.SaveMap;
}
}
And here's how you could use it to get the SaveMap
:
var saveMap = new NorthwindIBDoNotSaveContext().GetSaveMapFromSaveBundle(saveBundle);
Yes, it is "hacky", particularly if all you want is the SaveMap
. But why do you just want the SaveMap
?
We've designed the ContextProvider
(and all of its sub-classes) such that you have free reign over the SaveChangesCore
method. You could override that, further manipulate the SaveMap
, then either delegate to the base implementation or do whatever else you have in mind for saving the entity data.
But while I don't see what you're after, it was not all that hard to extract the SaveChanges
initialization logic into its own method.
So in the next release (after 1.5.2), you should find the following new method in the ContextProvider
:
protected void InitializeSaveState(JObject saveBundle)
{
JsonSerializer = CreateJsonSerializer();
var dynSaveBundle = (dynamic)saveBundle;
var entitiesArray = (JArray)dynSaveBundle.entities;
var dynSaveOptions = dynSaveBundle.saveOptions;
SaveOptions = (SaveOptions)JsonSerializer.Deserialize(new JTokenReader(dynSaveOptions), typeof(SaveOptions));
SaveWorkState = new SaveWorkState(this, entitiesArray);
}
SaveChanges
now calls that method before continuing on in its previous manner:
public SaveResult SaveChanges(JObject saveBundle, TransactionSettings transactionSettings = null) {
if (SaveWorkState == null || SaveWorkState.WasUsed) {
InitializeSaveState(saveBundle);
}
transactionSettings = transactionSettings ?? BreezeConfig.Instance.GetTransactionSettings();
...
}
Notice that SaveChanges
won't call InitializeSaveState
twice if you've already prepared the SaveWorkState
by, say, calling InitializeSaveState
externally and then called SaveChanges
immediately thereafter. It also won't save twice with a "used" SaveWorkState
.
The source is checked into github right now if you're interested.
You'll be able to get the SaveMap
from a save bundle by adding this method to your sub-class of a ContextProvider
as in this example:
public class NorthwindContextProvider: EFContextProvider<NorthwindIBContext_CF> {
...
public Dictionary<Type, List<EntityInfo>> GetSaveMapFromSaveBundle(JObject saveBundle) {
InitializeSaveState(saveBundle); // Sets initial EntityInfos
SaveWorkState.BeforeSave(); // Creates the SaveMap as byproduct of BeforeSave logic
return SaveWorkState.SaveMap;
}
...
}
Now you use that as follows:
var saveMap = ContextProvider.GetSaveMapFromSaveBundle(saveBundle);