.ToList на IQueryable вызывает исключение NullReferenceException при выполнении в фоновом режиме

Мой код работает нормально, когда выполняется синхронно, но происходит сбой при запуске через QueueBackgroundWorkItem.

ОБНОВЛЕНИЕ: проблема, кажется, связана с менеджером динамических модулей Sitefinity, который я использую, требуя HTTP-контекста в потоке, созданном QueueBackgroundWorkItem, так что это специфический для 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();
}

Класс DataSync:

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

Функция 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: 

1 ответ

Решение

Оказывается, это была проблема с Sitefinity CMS. Я запрашивал данные из-за необходимости HTTP-контекста, который, конечно, теряется в потоках. Исключение NullReferenceException происходило в методе dynamicModuleManager.GetDataItems().

С некоторой помощью друзей и замечательных участников SO, включая @Igor, @Hogan, @PeterBons и @daramasala, которые помогли мне понять проблему, которую я смог решить, подняв привилегии до SF и симулировав HttpContext в потоке.

http://www.sitefinity.com/developer-network/forums/sitefinity-sdk/errors-with-managers-when-multi-threading

Только SF документация вне форумов, которую я мог найти по использованию этого метода, находится здесь: http://docs.sitefinity.com/bug-tracker-create-the-savebug-action

Обновленный метод RunSync() в моем классе DataSync:

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);
}
Другие вопросы по тегам