Asp.net web api Breeze SaveBundleToSaveMap示例中未初始化的JsonSerializer

Asp.net web api Breeze SaveBundleToSaveMap示例中未初始化的JsonSerializer,asp.net-web-api,breeze,Asp.net Web Api,Breeze,我试图使用下面链接的SaveBundleToSaveMap片段在BreezeWebAPI实现的服务器端实现自定义保存处理 此示例不按原样工作?(见下文);它们是一个空引用异常,需要注意 SaveWorksState(provider,EntityArray)构造函数调用ContextProvider.CreateEntityInfoFromJson(…)方法,然后该方法调用(类作用域)JsonSerializer.Deserialize(新的JTokenReader(jo),entityTyp

我试图使用下面链接的SaveBundleToSaveMap片段在BreezeWebAPI实现的服务器端实现自定义保存处理

此示例不按原样工作?(见下文);它们是一个空引用异常,需要注意

SaveWorksState(provider,EntityArray)构造函数调用ContextProvider.CreateEntityInfoFromJson(…)方法,然后该方法调用(类作用域)JsonSerializer.Deserialize(新的JTokenReader(jo),entityType)方法

问题是JsonSerializer未初始化,我们得到了一个空引用。 例如,我添加了这个测试hack来运行代码:

protected internal EntityInfo CreateEntityInfoFromJson(dynamic jo, Type entityType) {
      //temp fix to init JsonSerializer if SaveChanges has NOT been called
      if(JsonSerializer==null) JsonSerializer = CreateJsonSerializer();

      var entityInfo = CreateEntityInfo();

      entityInfo.Entity = JsonSerializer.Deserialize(new JTokenReader(jo), entityType);
      entityInfo.EntityState = (EntityState)Enum.Parse(typeof(EntityState), (String)jo.entityAspect.entityState);
      entityInfo.ContextProvider = this;
这个问题不会出现在标准版本位中,因为CreateEntityInfoFromJson总是这样?从SaveChanges()调用下游调用,这意味着JsonSerializer将被初始化

但是,如果将初始化的JsonSerializer作为参数传递给CreateEntityInfoFromJson,以避免将来可能出现的空引用问题,那么结构会更好吗

或者,有没有办法让SaveBundleToSaveMap片段初始化JsonSerializer?它有一个私人setter:(

更新

实现了一个非常粗糙的权宜之计解决方案。如果iDeaBrade的任何人都在观看,最好有一个公共API来转换json saveBundle saveMap

/// <summary>
/// Convert a json saveBundle into a breeze SaveMap
/// </summary>`enter code here`
public static Dictionary<Type, List<EntityInfo>> SaveBundleToSaveMap(JObject saveBundle)
{
   var _dynSaveBundle = (dynamic)saveBundle;
   var _entitiesArray = (JArray)_dynSaveBundle.entities;
   var _provider = new BreezeAdapter();

   //Hack 1: Breeze.ContextProvider initializes a global JsonSerializer in its SaveChanges() method
   //We are bypassing SaveChanges() and bootstrapping directly into SaveWorkState logic to generate our saveMap
   //as such we need to init a serializer here and slipsteam it in via reflection (its got a private setter)
   var _serializerSettings = BreezeConfig.Instance.GetJsonSerializerSettings();
   var _bootstrappedJsonSerializer = JsonSerializer.Create(_serializerSettings);

   //Hack 2:
   //How to write to a private setter via reflection
   //http://stackoverflow.com/questions/3529270/how-can-a-private-member-accessable-in-derived-class-in-c
   PropertyInfo _jsonSerializerProperty = _provider.GetType().GetProperty("JsonSerializer", BindingFlags.Instance | BindingFlags.NonPublic);
   //Hack 3: JsonSerializer property is on Breeze.ContextProvider type; not our derived EFContextProvider type so...
   _jsonSerializerProperty =  _jsonSerializerProperty.DeclaringType.GetProperty("JsonSerializer", BindingFlags.Instance | BindingFlags.NonPublic);
   //Finally, we can init the JsonSerializer
   _jsonSerializerProperty.SetValue(_provider, _bootstrappedJsonSerializer);

   //saveWorkState constructor loads json entitiesArray into saveWorkState.EntityInfoGroups struct
   var _saveWorkState = new SaveWorkState(_provider, _entitiesArray);
   //BeforeSave logic loads saveWorkState.EntityInfoGroups metadata into saveWorkState.SaveMap 
   _saveWorkState.BeforeSave();
   var _saveMap = _saveWorkState.SaveMap; 

   return _saveMap;
}
//
///将json保存包转换为breeze保存映射
///”“在这里输入代码`
公共静态字典saveBundle保存映射(JObject saveBundle)
{
var _dynSaveBundle=(动态)saveBundle;
var _entitiesArray=(JArray)_dynSaveBundle.entities;
var_provider=new BreezeAdapter();
//Hack 1:Breeze.ContextProvider在其SaveChanges()方法中初始化全局JsonSerializer
//我们绕过SaveChanges()并直接引导到SaveWorkState逻辑来生成saveMap
//因此,我们需要在这里初始化一个序列化程序,并通过反射(它有一个私有setter)将其导入
var_serializerSettings=BreezeConfig.Instance.GetJsonSerializerSettings();
var _bootstrappedJsonSerializer=JsonSerializer.Create(_serializerSettings);
//黑客2:
//如何通过反射向私有setter写入
//http://stackoverflow.com/questions/3529270/how-can-a-private-member-accessable-in-derived-class-in-c
PropertyInfo _jsonSerializerProperty=_provider.GetType().GetProperty(“JsonSerializer”,BindingFlags.Instance | BindingFlags.NonPublic);
//Hack 3:JsonSerializer属性位于Breeze.ContextProvider类型上;不是派生的EFContextProvider类型,因此。。。
_jsonSerializerProperty=_jsonSerializerProperty.DeclaringType.GetProperty(“JsonSerializer”,BindingFlags.Instance | BindingFlags.NonPublic”);
//最后,我们可以初始化JsonSerializer
_SetValue(_provider,_bootstrappedJsonSerializer);
//saveWorkState构造函数将json entitiesArray加载到saveWorkState.EntityInfoGroups结构中
var _saveWorkState=新的saveWorkState(_provider,_entitiesArray);
//BeforeSave逻辑将saveWorkState.EntityInfoGroups元数据加载到saveWorkState.SaveMap中
_saveWorkState.BeforeSave();
var\u saveMap=\u saveWorkState.saveMap;
返回_saveMap;
}

我对此进行了研究。实际上,您不需要对Breeze代码进行更改即可完成所需操作。
ContextProvider
的设计使您可以在保存过程中执行任何您想要的操作

我很好奇:您想执行什么“自定义保存处理”,而今天使用BeforeSave和AfterSave逻辑无法执行?我在您的“权宜之计”代码中看到,您正在调用
SaveWorkState
上的
BeforeSave
。您还需要什么

作为练习,我写了一篇文章,它能满足你的需求。下面是它的运行方式:

/// <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;
  }
}
是的,它是“黑”的,特别是如果你只想要
SaveMap
。但是为什么你只想要
SaveMap

我们设计了
ContextProvider
(及其所有子类),这样您就可以自由支配
SaveChangesCore
方法。您可以覆盖该方法,进一步操作
SaveMap
,然后委托给基本实现,或者执行任何您想保存实体数据的操作

但是,虽然我不知道您想要的是什么,但将
SaveChanges
初始化逻辑提取到它自己的方法中并不难

因此,在下一个版本(1.5.2之后)中,您应该在
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
现在调用该方法,然后以以前的方式继续:

public SaveResult SaveChanges(JObject saveBundle, TransactionSettings transactionSettings = null) {

  if (SaveWorkState == null || SaveWorkState.WasUsed) {
    InitializeSaveState(saveBundle);
  }

  transactionSettings = transactionSettings ?? BreezeConfig.Instance.GetTransactionSettings();
  ...
}
请注意,
SaveChanges
不会调用
InitializeSaveState
两次,如果您已经准备好了
SaveWorkState
,比如从外部调用
InitializeSaveState
,然后立即调用
SaveChanges
。它也不会使用“已使用的”
SaveWorkState
保存两次

如果你感兴趣的话,消息来源就在现在

通过将此方法添加到
ContextProvider
子类中,您可以从保存包中获取
SaveMap
,如本例所示:

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;
  }
  ...
}

你知道了一些事情。我记得我自己也想在这里进行一些分离。我会再联系你的。谢谢沃德!你的解决方案非常有效。我对该功能的使用案例是,我有一个使用breeze控制器的现有系统(并使用JObject保存包公开一个操作)。我想截获此操作的有效负载,并在任何ContextProvider被命中之前接收JObject所关联的实体。我已经获取了您的NorthwindIBDoNotSaveContext示例,并将其包装在一个静态转换类中,现在我可以获得SaveResult和我想要的EntityInfo.entity。太棒了!一个建议n、 您提到您创建了一个静态包装器类。您有吗
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;
  }
  ...
}
  var saveMap =  ContextProvider.GetSaveMapFromSaveBundle(saveBundle);