Uninitialised JsonSerializer in Breeze SaveBundleToSaveMap sample

后端 未结 1 1894
孤独总比滥情好
孤独总比滥情好 2021-01-16 10:25

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

相关标签:
1条回答
  • 2021-01-16 10:42

    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);
    
    0 讨论(0)
提交回复
热议问题