Is that possible to retrieve products without passing catalogType? - sap-commerce-cloud

I have a requirement to get products by code without knowing catalogType. Is that possible to retrieve products without passing catalogType?
Below is the code snippet I've tried:
#Resource
private ProductDao productDao;
#Resource
private CatalogVersionService catalogVersionService;
List<ProductModel> getProductsByCode(String code) {
CatalogVersionModel catalogVersionModel = new CatalogVersionModel();
catalogVersionModel.setVersion("Online");
catalogVersionService.addSessionCatalogVersion(catalogVersionModel);
List<ProductModel> productModels = productDao.findProductsByCode(code);
}
Below is the exception am getting:
{
"errors": [
{
"message": "model CatalogVersionModel (<unsaved>) cannot be serialized due to being modified, new or removed",
"type": "FlexibleSearchError"
}
]
}
May I know how to fix for above issue?

When you create a product/variant in SAP Commerce (hybris) you must attach it to a catalog.
A catalog (CatalogModel) also have a version (usually staged or online), and the object is called a CatalogVersionModel
When you want to retrieve a product/variant, you must indicate the CatalogVersionModel because the product code is not a unique key to retrieve the product in the DB (you can check the Type "Product" in the backoffice and see in the XML pane that both code and catalogVersion have the value unique="true")
Now in you code there are several issue.
You should not create a catalog version but you should retrive it using a service (See DefaultCatalogVersionService)
You should use a service to retrieve your product (See DefaultProductService)
In productService implementation, you'll find two methods getProductForCode.
One with only the sku code as parameter
One with the sku code and catalogVersion as parameter
The first method actually looks like the method you want, but in fact, it uses the catalogVersion in your session. Your session will be different if you run your code in groovy or if you run your code in Java from your ecommerce website.
You can find the comment of this method below
Returns the Product with the specified code. As default the search uses the current session user, the currentsession language and the current active catalog versions (which are stored at the session in the attribute SESSION_CATALOG_VERSIONS).For modifying the search session context see FlexibleSearchQuery.

You need to specify the catalog, because it is possible to have multiple catalogs, and the same product could exist in all of those catalogs.

Related

How do I set the applicaiton insights operationID to my custom correlationID?

I have a .net6 worker service and I need the Request Telemetry OperationID set to a custom value. This value is my CorrelationID that is read from a message queue, and it's format is a guid with dashes.
TelemetryClient.StartOperation has an overload that takes an operationId, but it only works with a specific format. It will not work with my guid.
I have tried the code below, which appears to work in the debugger. However, the value that shows up in applicaiton insights is not what I set it to.
var client = new TelemetryClient();
client.Context.Operation.Id = internalId;
I have tried creating an ITelemetryInitializer. If I set the operationID in the initialize method will work. The problem here is getting the correlationID to the initializer in the correct dependency injection scope.
It seems as though this is a common scenario. I have seen where others ask the question but I have not found a suitable solution.
Has anyone solved this problem?
The problem is that since .Net 5 the default Id format is set to W3C standard instead of the Hierarchical Id format, see the docs:
Parent-Child relationships between Activities in the distributed trace tree are established using unique IDs. .NET's implementation of distributed tracing supports two ID schemes: the W3C standard TraceContext, which is the default in .NET 5+, and an older .NET convention called 'Hierarchical' that's available for backwards compatibility. Activity.DefaultIdFormat controls which ID scheme is used. In the W3C TraceContext standard, every trace is assigned a globally unique 16-byte trace-id (Activity.TraceId), and every Activity within the trace is assigned a unique 8-byte span-id (Activity.SpanId). Each Activity records the trace-id, its own span-id, and the span-id of its parent (Activity.ParentSpanId). Because distributed traces can track work across process boundaries, parent and child Activities may not be in the same process. The combination of a trace-id and parent span-id can uniquely identify the parent Activity globally, regardless of what process it resides in.
Activity.DefaultIdFormat controls which ID format is used for starting new traces, but by default adding a new Activity to an existing trace uses whatever format the parent Activity is using. Setting Activity.ForceDefaultIdFormat to true overrides this behavior and creates all new Activities with the DefaultIdFormat, even when the parent uses a different ID format.
When you set the Activity.DefaultIdFormat to ActivityIdFormat.Hierarchical you can specify any string as an operation Id as it does not have to conform to the W3C standard.
So the following code works like a charm:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical;
int index = 0;
while (!stoppingToken.IsCancellationRequested)
{
++index;
using var operation = _telemetryClient.StartOperation<RequestTelemetry>($"op{index}", $"a-b-c-{index}");
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
but it might break the distributed trace flow for your api controllers if you want to trace the end-to-end flow between multiple seperate applications.
Another way is to just include your own correlation Id as a custom propery:
operation.Telemetry.Properties["MessageCorrelationId"] = "xxx";

Kentico 11 - duplicate object is created after rollback

After I added versioning to a custom class (see https://devnet.kentico.com/articles/module-development-versioning-recycle-bin), the duplicated object is created (instead of just updating the existing one) when rollback feature is used.
Since I was asked for the exact steps that I took, here they are:
I have a custom class which is called StoreInfo. It contains a set of fields as Name, Address, Country, etc.
For enabling versioning, I added the following changes to the generated code:
public class StoreInfo : AbstractInfo<IndustryInfo>
{
...
public static ObjectTypeInfo TYPEINFO = new ObjectTypeInfo(...)
{
...
SupportsVersioning = true
};
...
protected override bool VersioningEnabled
{
get
{
return SettingsKeyInfoProvider.GetBoolValue("CMSEnableObjectsVersioning");
}
}
}
After this change was applied the Versions tab appeared in the UI interface:
Then I changed the name of the store from Test name to Test name 1, so Version 1.1 was added to the versions list:
The issue happened when I clicked this button:
Instead of updating the name of the existing Store back from Store name 1 to Store name it created a new store with the data of Version 1.0:
Any thoughts about why this happens would be helpful.
Kentico version is 11. (dev. approach - portal engine)
If you deleted Object 1 and then created a "new" Object 1 and tried to roll back the deletion of the original Object 1, it will create a new one because they don't have the same attributes, especially the GUID.
So you may need to specify exactly the steps you took when you deleted the object, what you did after you deleted the object and what you did when you rolled back the original object.

How to use ObjectContext with Model Builder?

Is there a way we can use ObjectContext with DbContext's ModelBuilder? We don't want to use POCO because we have customized property code that does not modify entire object in update, but only update modified properties. Also we have lots of serialisation and auditing code that uses EntityObject.
Since poco does create a proxy with EntityObject, we want our classes to be derived from EntityObject. We don't want proxy. We also heavily use CreateSourceQuery. The only problem is EDMX file and its big connection string syntax web.config.
Is there any way I can get rid of EDMX file? It will be useful as we can dynamically compile new class based on reverse engineering database.
I would also like to use DbContext with EntityObject instead of poco.
Internal Logic
Access Modified Properties in Save Changes which is available in ObjectStateEntry and Save them onto Audit with Old and New Values
Most of times we need to only check for Any condition on Navigation Property for example
User.EmailAddresses.CreateSourceQuery()
.Any( x=> x.EmailAddress == givenAddress);
Access Property Attributes, such as XmlIgnore etc, we rely heavily on attributes defined on the properties.
A proxy for a POCO is a dynamically created class which derives from (inherits) a POCO. It adds functionality previously found in EntityObject, namely lazy loading and change tracking, as long as a POCO meets requirements. A POCO or its proxy does not contain an EntityObject as the question suggests, but rather a proxy contains functionality of EntityObject. You cannot (AFAIK) use ModelBuilder with EntityObject derivatives and you cannot get to an underlying EntityObject from a POCO or a proxy, since there isn't one as such.
I don't know what features of ObjectContext does your existing serialisation and auditing code use, but you can get to ObjectContext from a DbContext by casting a DbContext to a IObjectContextAdapter and accessing IObjectContextAdapter.ObjectContext property.
EDIT:
1. Access Modified Properties in Save Changes which is available in ObjectStateEntry and Save them onto Audit with Old and New Values
You can achieve this with POCOs by using DbContext.ChangeTracker. First you call DbContext.ChangeTracker.DetectChanges to detect the changes (if you use proxies this is not needed, but can't hurt) and then you use DbCotnext.Entries.Where(e => e.State != EntityState.Unchanged && e.State != EntityState.Detached) to get DbEntityEntry list of changed entities for auditing. Each DbEntityEntry has OriginalValues and CurrentValues and the actual Entity is in property Entity.
You also have access to ObjectStateEntry, see below.
2. Most of times we need to only check for Any condition on Navigation Property for example:
User.EmailAddresses.CreateSourceQuery().Any( x=> x.EmailAddress == givenAddress);
You can use CreateSourceQuery() with DbContext by utilizing IObjectContextAdapter as described previously. When you have ObjectContext you can get to the source query for a related end like this:
public static class DbContextUtils
{
public static ObjectQuery<TMember> CreateSourceQuery<TEntity, TMember>(this IObjectContextAdapter adapter, TEntity entity, Expression<Func<TEntity, ICollection<TMember>>> memberSelector) where TMember : class
{
var objectStateManager = adapter.ObjectContext.ObjectStateManager;
var objectStateEntry = objectStateManager.GetObjectStateEntry(entity);
var relationshipManager = objectStateManager.GetRelationshipManager(entity);
var entityType = (EntityType)objectStateEntry.EntitySet.ElementType;
var navigationProperty = entityType.NavigationProperties[(memberSelector.Body as MemberExpression).Member.Name];
var relatedEnd = relationshipManager.GetRelatedEnd(navigationProperty.RelationshipType.FullName, navigationProperty.ToEndMember.Name);
return ((EntityCollection<TMember>)relatedEnd).CreateSourceQuery();
}
}
This method uses no dynamic code and is strongly typed since it uses expressions. You use it like this:
myDbContext.CreateSourceQuery(invoice, i => i.details);

How to auto-generate early bound properties for Entity specific (ie Local) Option Set text values?

After spending a year working with the Microsoft.Xrm.Sdk namespace, I just discovered yesterday the Entity.FormattedValues property contains the text value for Entity specific (ie Local) Option Set texts.
The reason I didn't discover it before, is there is no early bound method of getting the value. i.e. entity.new_myOptionSet is of type OptionSetValue which only contains the int value. You have to call entity.FormattedValues["new_myoptionset"] to get the string text value of the OptionSetValue.
Therefore, I'd like to get the crmsrvcutil to auto-generate a text property for local option sets. i.e. Along with Entity.new_myOptionSet being generated as it currently does, Entity.new_myOptionSetText would be generated as well.
I've looked into the Microsoft.Crm.Services.Utility.ICodeGenerationService, but that looks like it is mostly for specifying what CodeGenerationType something should be...
Is there a way supported way using CrmServiceUtil to add these properties, or am I better off writing a custom app that I can run that can generate these properties as a partial class to the auto-generated ones?
Edit - Example of the code that I would like to be generated
Currently, whenever I need to access the text value of a OptionSetValue, I use this code:
var textValue = OptionSetCache.GetText(service, entity, e => e.New_MyOptionSet);
The option set cache will use the entity.LogicalName, and the property expression to determine the name of the option set that I'm asking for. It will then query the SDK using the RetrieveAttriubteRequest, to get a list of the option set int and text values, which it then caches so it doesn't have to hit CRM again. It then looks up the int value of the New_MyOptionSet of the entity and cross references it with the cached list, to get the text value of the OptionSet.
Instead of doing all of that, I can just do this (assuming that the entity has been retrieved from the server, and not just populated client side):
var textValue = entity.FormattedValues["new_myoptionset"];
but the "new_myoptionset" is no longer early bound. I would like the early bound entity classes that gets generated to also generate an extra "Text" property for OptionSetValue properties that calls the above line, so my entity would have this added to it:
public string New_MyOptionSetText {
return this.GetFormattedAttributeValue("new_myoptionset"); // this is a protected method on the Entity class itself...
}
Could you utilize the CrmServiceUtil extension that will generate enums for your OptionSets and then add your new_myOptionSetText property to a partial class that compares the int value to the enums and returns the enum string
Again, I think specifically for this case, getting CrmSvcUtil.exe to generate the code you want is a great idea, but more generally, you can access the property name via reflection using an approach similar to the accepted answer # workarounds for nameof() operator in C#: typesafe databinding.
var textValue = entity.FormattedValues["new_myoptionset"];
// becomes
var textValue = entity.FormattedValues
[
// renamed the class from Nameof to NameOf
NameOf(Xrm.MyEntity).Property(x => x.new_MyOptionSet).ToLower()
];
The latest version of the CRM Early Bound Generator includes a Fields struct that that contains the field names. This allows accessing the FormattedValues to be as simple as this:
var textValue = entity.FormattedValues[MyEntity.Fields.new_MyOptionSet];
You could create a new property via an interface for the CrmSvcUtil, but that's a lot of work for a fairly simple call, and I don't think it justifies creating additional properties.

DDD: keep a link to an entity inside an aggregate root, for reporting only

I'm refactoring a project using DDD, but am concerned about not making too many Entities their own Aggregate Root.
I have a Store, which has a list of ProductOptions and a list of Products. A ProductOption can be used by several Products. These entities seem to fit the Store aggregate pretty well.
Then I have an Order, which transiently uses a Product to build its OrderLines:
class Order {
// ...
public function addOrderLine(Product $product, $quantity) {
$orderLine = new OrderLine($product, $quantity);
$this->orderLines->add($orderLine);
}
}
class OrderLine {
// ...
public function __construct(Product $product, $quantity) {
$this->productName = $product->getName();
$this->basePrice = $product->getPrice();
$this->quantity = $quantity;
}
}
Looks like for now, DDD rules as respected. But I'd like to add a requirement, that might break the rules of the aggregate: the Store owner will sometimes need to check statistics about the Orders which included a particular Product.
That means that basically, we would need to keep a reference to the Product in the OrderLine, but this would never be used by any method inside the entity. We would only use this information for reporting purposes, when querying the database; thus it would not be possible to "break" anything inside the Store aggregate because of this internal reference:
class OrderLine {
// ...
public function __construct(Product $product, $quantity) {
$this->productName = $product->getName();
$this->basePrice = $product->getPrice();
$this->quantity = $quantity;
// store this information, but don't use it in any method
$this->product = $product;
}
}
Does this simple requirement dictates that Product becomes an aggregate root? That would also cascade to the ProductOption becoming an aggregate root, as Product has a reference to it, thus resulting in two aggregates which have no meaning outside a Store, and will not need any Repository; looks weird to me.
Any comment is welcome!
Even though it is for 'reporting only' there is still a business / domain meaning there. I think that your design is good. Although I would not handle the new requirement by storing OrderLine -> Product reference. I would do something similar to what you already doing with product name and price. You just need to store some sort of product identifier (SKU?) in the order line. This identifier/SKU can later be used in a query. SKU can be a combination of Store and Product natural keys:
class Sku {
private String _storeNumber;
private String _someProductIdUniqueWithinStore;
}
class OrderLine {
private Money _price;
private int _quantity;
private String _productName;
private Sku _productSku;
}
This way you don't violate any aggregate rules and the product and stores can be safely deleted without affecting existing or archived orders. And you can still have your 'Orders with ProductX from StoreY'.
Update: Regarding your concern about foreign key. In my opinion foreign key is just a mechanism that enforces long-living Domain relationships at the database level. Since you don't have a domain relationship you don't need the enforcement mechanism as well.
In this case you need the information for reporting which has nothing to do with the aggregate root.
So the most suitable place for it would be a service (could be a domain service if it is related to business or better to application service like querying service which query the required data and return them as DTOs customizable for presentation or consumer.
I suggest you create a statistics services which query the required data using read only repositories (or preferable Finders) which returns DTOs instead of corrupting the domain with query models.
Check this

Resources