I resently noticed that whenever something happens that makes the site restart, the LineItemID of a LineItem in EPiServer Commerce 10, is reset. For example, if I:
When adding the third item I would expect the LineItemID for the new item to be -3, However it is -1.
Is this a bug? I would expect that each lineItem would be unique in the cart, even if the site is restarted.
Thanks for report that. It's a known issue. This happens with the new cart mode (serializable cart), where all cart data is stored in only one column of the SerializableCart table in the database. The important thing when generating id for line item/shipment is that the id should be unique not only in a cart but also among all carts. This case is not very common but we'll look into that. A workaround is that you can check the line item id when adding item to the cart, the shipment id when adding shipment to the cart, and then assign an unique value for them.
This issue has beeen fixed in version 10.7.1, released today.
I think I have an issue with your fix :)
We are just trying to upgrade to Commerce 10.7.1 from 10.4.2. Everything went fine on our development environment, but our Test environment broke down with the errormessage below.
After a lot of debugging and reverse engineering of your code, we found the problem. In our test environment we had a number of old SerializableCarts stored with orderFormId:0 in the Data JSON. These carts break your initialization, because the SerializableOrderForm.get_OrderFormId looks at the _orderFormId property. If this field is 0, then it tries to call the _serializableIdGenerator. But that property can be null, if called from the SerializableIdGenerator.InitSerializableIdGenerator() method (the method is called during construction where it is not populated yet).
It may be an error that orderFormId is 0 in the database, but in our situation we had 666 carts in our test database with this information. They have been created during an earlier version of Episerver Commerce, and they have most likely been created in error. But nevertheless they existed. And they may also exist at some of your other customers who have not yet upgraded to 10.7.1
--- ERROR MESSAGE ---
2017-06-07 12:38:44,248  ERROR EPiServer.Global: 1.2.5 Unhandled exception in ASP.NETEPiServer.ServiceLocation.ActivationException: Activation error occurred while trying to get instance of type IProfileMigrator, key "" ---> StructureMap.Building.StructureMapBuildException: Error while building type EPiServer.Commerce.Order.Internal.SerializableIdGenerator. See the inner exception for details1.) new SerializableIdGenerator(*Default of SerializableCartDB*)2.) EPiServer.Commerce.Order.Internal.SerializableIdGenerator3.) Instance of EPiServer.Commerce.Order.Internal.SerializableIdGenerator4.) new SerializableCartBuilder(*Default of SerializableIdGenerator*)5.) EPiServer.Commerce.Order.Internal.SerializableCartBuilder6.) Instance of EPiServer.Commerce.Order.IOrderGroupBuilder (EPiServer.Commerce.Order.Internal.SerializableCartBuilder)7.) new OrderGroupFactory(Enumerable of EPiServer.Commerce.Order.IOrderGroupBuilder with all registered instances)8.) EPiServer.Commerce.Orders.Internal.OrderGroupFactory9.) Instance of EPiServer.Commerce.Order.IOrderGroupFactory (EPiServer.Commerce.Orders.Internal.OrderGroupFactory)10.) new SerializableCartProvider(*Default of OperationKeysTransformer*, *Default of SerializableCartDB*, *Default of ServiceAccessor<SiteContext>*, *Default of ICurrentMarket*, *Default of IPurchaseOrderProvider*, *Default of IPaymentPlanProvider*, *Default of IOrderGroupFactory*, *Default of IOrderNumberGenerator*)11.) EPiServer.Commerce.Order.Internal.SerializableCartProvider12.) Instance of EPiServer.Commerce.Order.ICartProvider (EPiServer.Commerce.Order.Internal.SerializableCartProvider)13.) new DefaultOrderRepository(*Default of ICartProvider*, *Default of IPurchaseOrderProvider*, *Default of IPaymentPlanProvider*, *Default of ISynchronizedObjectInstanceCache*, Enumerable of EPiServer.Commerce.Order.IOrderRepositoryCallback with all registered instances)14.) EPiServer.Commerce.Order.DefaultOrderRepository15.) Instance of EPiServer.Commerce.Order.IOrderRepository (EPiServer.Commerce.Order.DefaultOrderRepository)16.) new ProfileMigrator(*Default of IOrderRepository*, *Default of ICurrentMarket*, *Default of CartMigrator*)17.) EPiServer.Commerce.Order.ProfileMigrator18.) Instance of EPiServer.Commerce.Order.IProfileMigrator (EPiServer.Commerce.Order.ProfileMigrator)19.) Container.GetInstance(EPiServer.Commerce.Order.IProfileMigrator) ---> System.NullReferenceException: Object reference not set to an instance of an object. at EPiServer.Commerce.Order.Internal.SerializableOrderForm.get_OrderFormId() at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext() at System.Linq.Enumerable.Min(IEnumerable`1 source) at EPiServer.Commerce.Order.Internal.SerializableIdGenerator.InitSerializableIdGenerator() at lambda_method(Closure , IBuildSession , IContext ) --- End of inner exception stack trace --- at lambda_method(Closure , IBuildSession , IContext ) at StructureMap.Building.BuildPlan.Build(IBuildSession session, IContext context) at StructureMap.Pipeline.LifecycleObjectCache.Get(Type pluginType, Instance instance, IBuildSession session) at StructureMap.SessionCache.GetObject(Type pluginType, Instance instance, ILifecycle lifecycle) at StructureMap.SessionCache.GetDefault(Type pluginType, IPipelineGraph pipelineGraph) at StructureMap.Container.GetInstance(Type pluginType) at EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key) --- End of inner exception stack trace --- at EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key) at EPiServer.ServiceLocation.ServiceLocatorImplBase.GetInstance[TService]() at EPiServer.ServiceLocation.Injected`1.get_Service() at EPiServer.Business.Commerce.HttpModules.ProfileModule.Profile_MigrateAnonymous(Object sender, ProfileMigrateEventArgs pe) at System.Web.Profile.ProfileModule.OnEnter(Object source, EventArgs eventArgs) at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
Anders Kåre Olsen
Thanks for your feedback. We'll look into this problem ASAP.
I have a feeling that the ID generation process doesn't work properly in a load-balanced environment.
During initialisation, the SerializableIdGenerator retrieves all carts and determines the lowest IDs for LineItem, Shipment, OrderForm and OrderNote. It offers a few methods to get a new ID, which are called by the getters of the respective classes (such as SerializableLineItem.LineItemId). According to StructureMap, the SerializableIdGenerator is a Singleton. Because of this, the class' initialization logic is called only once and ID tracking takes place in-memory afterwards.
I don't see any events being raised to alert other instances when a new line-item ID has been issued, so I suppose that it's still possible to have duplicate IDs being generated when a load-balanced environment is being used.
@Anders: sorry for very late response. The problem with order form id being 0 were found and fixed in version 10.7.2 (see COM-4680). Could you please upgrade to 10.7.2 to see if it still happens in your site?
@AlexN: Thank you for bring it to us, we'll look into it. However it should not be a critical problem, the IDs could be duplicated among carts, but the important thing is that IDs are unique in a single cart. And when saving carts to purchase orders, then the IDs will be unique in all orders.
That's good to hear.
It does make me wonder: what happens when you're using a load-balanced environment (such as a multi-instance Azure app service) with user sessions not tied to a particular server? Doesn't that make it possible for a line item to be created in the same cart with a duplicate ID, or am I missing something?
That's true. In a load-balanced environment, the load balancer distributes client requests or network load efficiently across multiple servers. So it's possible that requests of the same client were processed by different servers, and if those requests were for adding line items to a cart then the line item id might be duplicated. It seems to be an edge case however. But we'll look into it.