Five New Optimizely Certifications are Here! Validate your expertise and advance your career with our latest certification exams. Click here to find out more
Five New Optimizely Certifications are Here! Validate your expertise and advance your career with our latest certification exams. Click here to find out more
I think (just a thought, nothing concrete ) that the problem is BusinessManager will try to access the HttpContext which will be shared between the threads. even if you don't have that kind of problem you might run into other (HttpContext is not thread safe IIRC).
Why do you need to load the contacts in parallel? Can you show your code code GetDiscountedPrice(String code) ?
Hi Quan,
The shortened version of Project.Commerce.Core.Prices.Services.PricingService.GetDiscountedPrice(String code)
public IPriceValue? GetDiscountedPrice(string code)
{
// (skipped) getting contentReference from code
var currentMarket = _currentMarket.GetCurrentMarket();
var discountedPrices = _promotionEngine.GetDiscountPrices(new []{contentReference}, currentMarket, currentMarket.DefaultCurrency).ToList();
// (skipped) more logic
return priceValue;
}
And the _promotionEngine.GetDiscountPrices
is just a call to the standard EPiServer.Commerce.Marketing.IPromotionEngineExtensions.GetDiscountPrices
.
We don't use the contacts at all (in this scenario), but calling the IPromotionEngineExtensions.GetDiscountPrices
calls internally loading contact with BusinessManager.Load
and that's where the exception occurs. I tried to go down the chain to isolate what exactly is causing exception, that's why I focused on BusinessManager.Load
.
I think you can preload the contact using CustomerContext.Current.GetContactById, which would cache the contact and avoid calling to BusinessManager.Load
Not every API is meant to be run in parallel
Hi,
As in title - the
BusinessManager.Load
throwsNullReferenceException
when called withinIBackgroundContext
.The
BusinessManager.Load
method is called byIPromotionEngineExtensions.GetDiscountPrices
within a service, that we'd like to parallelize usingIBackgroundContext
andTask.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 theBusinessManager.Load
) withinIBackgroundContext
?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