IQueryable 上的 .ToList 在后台执行时会导致 NullReferenceException
.ToList on IQueryable causes NullReferenceException when executed in background
我的代码在同步执行时工作正常,但在通过 QueueBackgroundWorkItem 触发时失败。
更新:问题似乎与我正在使用的 Sitefinity 动态模块管理器有关,需要在 QueueBackgroundWorkItem 创建的线程中使用 HTTP 上下文,因此这是 Sitefinity 特定的问题。我找到了一篇似乎指向解决方案的文章:http://www.sitefinity.com/developer-network/forums/sitefinity-sdk/errors-with-managers-when-multi-threading
我的代码的相关部分
启动后台作业的 ApiController 操作:
// GET: api/Sync/MFP
[HttpGet]
public IHttpActionResult MFP()
{
HostingEnvironment.QueueBackgroundWorkItem(ct => startMFPSync());
return Ok("Sync Started!");
}
private void startMFPSync()
{
var sourcesConfig = Config.Get<SyncSourcesSettingsConfig>();
MFPApi api = new MFPApi(new MFPConfig
{
Url = sourcesConfig.MFPUrl,
Key = sourcesConfig.MFPKey,
Password = sourcesConfig.MFPPassword,
});
DataSync dataSync = new DataSync(api);
dataSync.RunSync();
}
数据同步class:
public class DataSync
{
IDataSource dataSource;
public DataSync(IDataSource source)
{
dataSource = source;
}
public void RunSync()
{
dataSource.GetResponse();
List<SyncContent> dataToSync = dataSource.GetDataForSync();
SFDynamicModuleSync destinationSync = new SFDynamicModuleSync(dataToSync);
// function call where exception occurs
destinationSync.CacheModuleData();
// other sync operations
}
}
下面class中的CacheModuleData()函数是发生异常的地方:
public class SFDynamicModuleSync : IDataDestinationSync
{
/// <summary>
/// List of SyncContent objects to sync
/// </summary>
private List<SyncContent> dataToSync;
/// <summary>
/// Used to store results of CacheModuleData
/// </summary>
private List<List<DynamicContent>> modulesItems;
/// <summary>
/// Sitefinity dynamic module manager
/// </summary>
private DynamicModuleManager dynamicModuleManager;
/// <summary>
/// Initializes a new instance of the <see cref="SFDynamicModuleSync"/> class
/// </summary>
/// <param name="dataToSync">List of SyncContent objects to sync</param>
public SFDynamicModuleSync(List<SyncContent> dataToSync)
{
this.dataToSync = dataToSync;
this.modulesItems = new List<List<DynamicContent>>();
this.dynamicModuleManager = DynamicModuleManager.GetManager();
}
/// <summary>
/// Retrieves all data from dynamic modules and places in modulesItems
/// </summary>
public void CacheModuleData()
{
foreach (string contentType in this.dataToSync.Select(e => e.ContentTypeName))
{
Type type = TypeResolutionService.ResolveType(contentType);
IQueryable<DynamicContent> moduleItems = this.dynamicModuleManager.GetDataItems(type)
.Where(i => i.Status == ContentLifecycleStatus.Master);
if(moduleItems != null)
{
// The .ToList() here causes a NullReferenceException when code is triggered by background job
List<DynamicContent> moduleItemsList = moduleItems.ToList();
this.modulesItems.Add(moduleItemsList);
}
}
}
// other sync methods - not included here for abbrevity
}
堆栈跟踪:
System.NullReferenceException was unhandled by user code
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=Unity_ILEmit_DynamicClasses
StackTrace:
at DynamicModule.ns.Wrapped_OpenAccessDynamicModuleProvider_81d3fcbe95dd4a47b8c1cb1cc5a692ab.ApplyFilters(IDataItem item)
at Telerik.Sitefinity.Security.FieldsPermissionsApplierEnumerator`1.Demand(T forItem)
at Telerik.Sitefinity.Security.PermissionApplierEnumeratorBase`1.MoveNext()
at Telerik.Sitefinity.Data.Linq.DataItemEnumerator`1.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at TeamSI.Sitefinity.DataSync.DataDestinations.SFDynamicModuleSync.CacheModuleData() in C:\Projects\SIEQ\TeamSI.Sitefinity.DataSync\DataDestinations\SFDynamicModuleSync.cs:line 75
at TeamSI.Sitefinity.DataSync.DataSync.RunSync() in C:\Projects\SIEQ\TeamSI.Sitefinity.DataSync\DataSync.cs:line 28
at SitefinityWebApp.Mvc.Controllers.SyncController.startMFPSync() in C:\Projects\SIEQ\Main_Site\Mvc\Controllers\SyncController.cs:line 72
at SitefinityWebApp.Mvc.Controllers.SyncController.<MFP>b__1_0(CancellationToken ct) in C:\Projects\SIEQ\Main_Site\Mvc\Controllers\SyncController.cs:line 52
at System.Web.Hosting.HostingEnvironment.<>c__DisplayClass91_0.<QueueBackgroundWorkItem>b__0(CancellationToken ct)
at System.Web.Hosting.BackgroundWorkScheduler.<RunWorkItemImpl>d__7.MoveNext()
InnerException:
原来这是 Sitefinity CMS 的一个问题,我正在查询需要 HTTP 上下文的数据,这当然在线程中丢失了。 NullReferenceException 发生在 dynamicModuleManager.GetDataItems() 方法中。
在朋友和很棒的 SO 贡献者的帮助下,包括@Igor、@Hogan、@PeterBons 和@daramasala,他们帮助我理解了这个问题,我能够通过提升 SF 的权限并在线程中模拟 HttpContext 来解决它。
我只能在论坛之外找到关于使用此方法的 SF 文档:
http://docs.sitefinity.com/bug-tracker-create-the-savebug-action
更新了我的 DataSync 中的 RunSync() 方法 class:
public void RunSync()
{
SystemManager.RunWithElevatedPrivilegeDelegate worker = new SystemManager.RunWithElevatedPrivilegeDelegate(args => {
dataSource.GetResponse();
List<SyncContent> dataToSync = dataSource.GetDataForSync();
var destinationSync = new SFDynamicModuleSync(dataToSync);
destinationSync.CacheModuleData();
// complete sync operations for each content type
for (int i = 0; i < dataToSync.Count; i++)
{
destinationSync.DeleteOldItems(i);
destinationSync.AddItems(i);
destinationSync.UpdateItems(i);
}
});
SystemManager.RunWithElevatedPrivilege(worker);
}
我的代码在同步执行时工作正常,但在通过 QueueBackgroundWorkItem 触发时失败。
更新:问题似乎与我正在使用的 Sitefinity 动态模块管理器有关,需要在 QueueBackgroundWorkItem 创建的线程中使用 HTTP 上下文,因此这是 Sitefinity 特定的问题。我找到了一篇似乎指向解决方案的文章:http://www.sitefinity.com/developer-network/forums/sitefinity-sdk/errors-with-managers-when-multi-threading
我的代码的相关部分
启动后台作业的 ApiController 操作:
// GET: api/Sync/MFP
[HttpGet]
public IHttpActionResult MFP()
{
HostingEnvironment.QueueBackgroundWorkItem(ct => startMFPSync());
return Ok("Sync Started!");
}
private void startMFPSync()
{
var sourcesConfig = Config.Get<SyncSourcesSettingsConfig>();
MFPApi api = new MFPApi(new MFPConfig
{
Url = sourcesConfig.MFPUrl,
Key = sourcesConfig.MFPKey,
Password = sourcesConfig.MFPPassword,
});
DataSync dataSync = new DataSync(api);
dataSync.RunSync();
}
数据同步class:
public class DataSync
{
IDataSource dataSource;
public DataSync(IDataSource source)
{
dataSource = source;
}
public void RunSync()
{
dataSource.GetResponse();
List<SyncContent> dataToSync = dataSource.GetDataForSync();
SFDynamicModuleSync destinationSync = new SFDynamicModuleSync(dataToSync);
// function call where exception occurs
destinationSync.CacheModuleData();
// other sync operations
}
}
下面class中的CacheModuleData()函数是发生异常的地方:
public class SFDynamicModuleSync : IDataDestinationSync
{
/// <summary>
/// List of SyncContent objects to sync
/// </summary>
private List<SyncContent> dataToSync;
/// <summary>
/// Used to store results of CacheModuleData
/// </summary>
private List<List<DynamicContent>> modulesItems;
/// <summary>
/// Sitefinity dynamic module manager
/// </summary>
private DynamicModuleManager dynamicModuleManager;
/// <summary>
/// Initializes a new instance of the <see cref="SFDynamicModuleSync"/> class
/// </summary>
/// <param name="dataToSync">List of SyncContent objects to sync</param>
public SFDynamicModuleSync(List<SyncContent> dataToSync)
{
this.dataToSync = dataToSync;
this.modulesItems = new List<List<DynamicContent>>();
this.dynamicModuleManager = DynamicModuleManager.GetManager();
}
/// <summary>
/// Retrieves all data from dynamic modules and places in modulesItems
/// </summary>
public void CacheModuleData()
{
foreach (string contentType in this.dataToSync.Select(e => e.ContentTypeName))
{
Type type = TypeResolutionService.ResolveType(contentType);
IQueryable<DynamicContent> moduleItems = this.dynamicModuleManager.GetDataItems(type)
.Where(i => i.Status == ContentLifecycleStatus.Master);
if(moduleItems != null)
{
// The .ToList() here causes a NullReferenceException when code is triggered by background job
List<DynamicContent> moduleItemsList = moduleItems.ToList();
this.modulesItems.Add(moduleItemsList);
}
}
}
// other sync methods - not included here for abbrevity
}
堆栈跟踪:
System.NullReferenceException was unhandled by user code
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=Unity_ILEmit_DynamicClasses
StackTrace:
at DynamicModule.ns.Wrapped_OpenAccessDynamicModuleProvider_81d3fcbe95dd4a47b8c1cb1cc5a692ab.ApplyFilters(IDataItem item)
at Telerik.Sitefinity.Security.FieldsPermissionsApplierEnumerator`1.Demand(T forItem)
at Telerik.Sitefinity.Security.PermissionApplierEnumeratorBase`1.MoveNext()
at Telerik.Sitefinity.Data.Linq.DataItemEnumerator`1.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at TeamSI.Sitefinity.DataSync.DataDestinations.SFDynamicModuleSync.CacheModuleData() in C:\Projects\SIEQ\TeamSI.Sitefinity.DataSync\DataDestinations\SFDynamicModuleSync.cs:line 75
at TeamSI.Sitefinity.DataSync.DataSync.RunSync() in C:\Projects\SIEQ\TeamSI.Sitefinity.DataSync\DataSync.cs:line 28
at SitefinityWebApp.Mvc.Controllers.SyncController.startMFPSync() in C:\Projects\SIEQ\Main_Site\Mvc\Controllers\SyncController.cs:line 72
at SitefinityWebApp.Mvc.Controllers.SyncController.<MFP>b__1_0(CancellationToken ct) in C:\Projects\SIEQ\Main_Site\Mvc\Controllers\SyncController.cs:line 52
at System.Web.Hosting.HostingEnvironment.<>c__DisplayClass91_0.<QueueBackgroundWorkItem>b__0(CancellationToken ct)
at System.Web.Hosting.BackgroundWorkScheduler.<RunWorkItemImpl>d__7.MoveNext()
InnerException:
原来这是 Sitefinity CMS 的一个问题,我正在查询需要 HTTP 上下文的数据,这当然在线程中丢失了。 NullReferenceException 发生在 dynamicModuleManager.GetDataItems() 方法中。
在朋友和很棒的 SO 贡献者的帮助下,包括@Igor、@Hogan、@PeterBons 和@daramasala,他们帮助我理解了这个问题,我能够通过提升 SF 的权限并在线程中模拟 HttpContext 来解决它。
我只能在论坛之外找到关于使用此方法的 SF 文档: http://docs.sitefinity.com/bug-tracker-create-the-savebug-action
更新了我的 DataSync 中的 RunSync() 方法 class:
public void RunSync()
{
SystemManager.RunWithElevatedPrivilegeDelegate worker = new SystemManager.RunWithElevatedPrivilegeDelegate(args => {
dataSource.GetResponse();
List<SyncContent> dataToSync = dataSource.GetDataForSync();
var destinationSync = new SFDynamicModuleSync(dataToSync);
destinationSync.CacheModuleData();
// complete sync operations for each content type
for (int i = 0; i < dataToSync.Count; i++)
{
destinationSync.DeleteOldItems(i);
destinationSync.AddItems(i);
destinationSync.UpdateItems(i);
}
});
SystemManager.RunWithElevatedPrivilege(worker);
}