Hi,
As in title - the BusinessManager.Load
throws NullReferenceException
when called within IBackgroundContext
.
The BusinessManager.Load
method is called by IPromotionEngineExtensions.GetDiscountPrices
within a service, that we'd like to parallelize using IBackgroundContext
and Task.WhenAll
.
On the other hand, if we're not using IBackgroundContext
, we're getting errors mentioned in:
https://world.optimizely.com/blogs/Johan-Bjornfot/Dates1/2023/8/parallel-tasks-and-backgroundcontext/
Is the there a way to utilize the GetDiscountPrices
(and by extent the BusinessManager.Load
) within IBackgroundContext
?
Code to reproduce:
var range = Enumerable.Range(0, 10).Select(_ => "4263b71a-69a4-43ff-b9a1-59a1d0cfbf9a"); // valid Customer Contact id
var customerContactTasks = range.Select(reference => Task.Run(() =>
{
using var backgroundContext = _backgroundContextFactory.Create();
// example to reproduce the problem, here is requested service which relies on BusinessManager.Load
// using var service = backgroundContext.Services.GetRequiredService<IProblematicService>();
// and then the service is called, internally using BusinessManager.Load via IPromotionEngine / IPromotionEngineExtensions
var contactId = new PrimaryKeyId(new Guid(reference));
var customerContact = BusinessManager.Load("Contact", contactId) as CustomerContact;
return customerContact;
}));
var customerContacts = await Task.WhenAll(customerContactTasks);
The stack trace of above example:
System.NullReferenceException: Object reference not set to an instance of an object.
at Mediachase.BusinessFoundation.Data.Business.BusinessManager.Execute(Request request)
at Mediachase.BusinessFoundation.Data.Business.BusinessManager.Load(String metaClassName, PrimaryKeyId primaryKeyId)
at Project.Commerce.Features.Start.Builders.SomeViewModelBuilder.<>c__DisplayClass4_0.<Build>b__4() in C:\repo\src\Project.Commerce\Features\Start\Builders\SomeViewModelBuilder.cs:line 42
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.<>c.<.cctor>b__272_0(Object obj)
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
The stack trace of real-world example (anonimized):
System.NullReferenceException: Object reference not set to an instance of an object.
at Mediachase.BusinessFoundation.Data.Business.BusinessManager.Load(String metaClassName, PrimaryKeyId primaryKeyId)
at Mediachase.Commerce.Customers.CustomerContext.InnerGetContactById(Guid contactId)
at Mediachase.Commerce.Customers.CustomerContext.<>c__DisplayClass32_0.<GetContactById>b__0()
at Mediachase.Commerce.Customers.CustomersCache.<>c__DisplayClass6_0`1.<ReadThrough>g__WrapperAction|0()
at EPiServer.Framework.Cache.ObjectInstanceCacheExtensions.ReadThrough[T](IObjectInstanceCache cache, String key, Func`1 readValue, Func`2 evictionPolicy)
at EPiServer.Framework.Cache.ObjectInstanceCacheExtensions.ReadThrough[T](IObjectInstanceCache cache, String key, Func`1 readValue, Func`1 evictionPolicy)
at Mediachase.Commerce.Extensions.ObjectInstanceCacheExtensions.ReadThrough[T](IObjectInstanceCache cache, Boolean useCache, String cacheKey, IEnumerable`1 masterKeys, TimeSpan duration, Func`1 load)
at Mediachase.Commerce.Extensions.ObjectInstanceCacheExtensions.ReadThrough[T](IObjectInstanceCache cache, String cacheKey, IEnumerable`1 masterKeys, TimeSpan duration, Func`1 load)
at Mediachase.Commerce.Customers.CustomersCache.ReadThrough[T](String key, IEnumerable`1 masterKeys, TimeSpan timeout, Func`1 load)
at Mediachase.Commerce.Customers.CustomerContext.ExecuteThroughCache[T](String key, TimeSpan timeout, Func`1 load)
at Mediachase.Commerce.Customers.CustomerContext.GetContactById(Guid contactId)
at EPiServer.Commerce.Marketing.RedemptionLimitService.GetContactById(Guid customerId)
at EPiServer.Commerce.Marketing.RedemptionLimitService.GetPerPromotionAndCustomerLimit(IEnumerable`1 promotions, Guid customerId, Int32 orderFormId)
at EPiServer.Commerce.Marketing.RedemptionLimitService.GetRemainingRedemptions(IEnumerable`1 promotions, Guid customerId, Int32 orderFormId)
at EPiServer.Commerce.Marketing.RedemptionLimits.Load(IEnumerable`1 promotions, Guid customerId, Int32 orderformId)
at EPiServer.Commerce.Marketing.PromotionEngine.Run(IOrderGroup orderGroup, PromotionEngineSettings settings)
at EPiServer.Commerce.Marketing.PromotionEngine.Evaluate(IEnumerable`1 entryLinks, IMarket market, Currency currency, RequestFulfillmentStatus requestFulfillmentStatus)
at EPiServer.Commerce.Marketing.IPromotionEngineExtensions.Evaluate(IPromotionEngine promotionEngine, IEnumerable`1 entryLinks, IMarket market, Currency currency, RequestFulfillmentStatus requestFulfillmentStatus)
at EPiServer.Commerce.Marketing.IPromotionEngineExtensions.GetDiscountedEntries(IPromotionEngine promotionEngine, ContentReference entryLink, IMarket market, Currency currency, ReferenceConverter referenceConverter, ILineItemCalculator lineItemCalculator)
at EPiServer.Commerce.Marketing.IPromotionEngineExtensions.<>c__DisplayClass27_0.<ReadThroughCache>b__0()
at EPiServer.Framework.Cache.ObjectInstanceCacheExtensions.ReadThrough[T](IObjectInstanceCache cache, String key, Func`1 readValue, Func`2 evictionPolicy)
at EPiServer.Framework.Cache.ObjectInstanceCacheExtensions.ReadThrough[T](IObjectInstanceCache cache, String key, Func`1 readValue, Func`1 evictionPolicy)
at EPiServer.Commerce.Marketing.IPromotionEngineExtensions.ReadThroughCache(IPromotionEngine promotionEngine, ContentReference contentLink, IMarket market, Currency currency, Guid contextId, ReferenceConverter referenceConverter, ILineItemCalculator lineItemCalculator, Func`5 getCacheKey, Func`7 readValues)
at EPiServer.Commerce.Marketing.IPromotionEngineExtensions.GetDiscountPrices(IPromotionEngine promotionEngine, IEnumerable`1 entryLinks, IMarket market, Currency marketCurrency, ReferenceConverter referenceConverter, ILineItemCalculator lineItemCalculator)
at EPiServer.Commerce.Marketing.IPromotionEngineExtensions.GetDiscountPrices(IPromotionEngine promotionEngine, IEnumerable`1 entryLinks, IMarket market, Currency marketCurrency)
at Project.Commerce.Core.Prices.Services.PricingService.GetDiscountedPrice(String code) in C:\repo\src\Project.Commerce.Core\Prices\Services\PricingService.cs:line 54
at Project.Commerce.Features.Product.Builders.SomeVariationViewModelBuilder.BuildDiscountedPrice(SomeVariationViewModel viewModel, SomeVariation variation, UserInfo user) in C:\repo\src\Project.Commerce\Features\Product\Builders\SomeVariationViewModelBuilder.cs:line 128
at Project.Commerce.Features.Product.Builders.SomeVariationViewModelBuilder.Build(SomeVariationViewModel viewModel, SomeVariation variation, IIdentity identity) in C:\repo\src\Project.Commerce\Features\Product\Builders\SomeVariationViewModelBuilder.cs:line 82