SaaS CMS has officially launched! Learn more now.

Replacing CatalogContext with a repository

Vote:
 

Hi,

I've heard that "contexts" such as CatalogContext are now considered legacy.

So I'd like to replace all usages of CatalogContext in my code.

What is the correct way to replace the following by using repository:


var entryInfo = CatalogContext.Current.GetCatalogEntry(code, new CatalogEntryResponseGroup(CatalogEntryResponseGroup.ResponseGroup.CatalogEntryInfo));

var entryFull = CatalogContext.Current.GetCatalogEntry(code, new CatalogEntryResponseGroup(CatalogEntryResponseGroup.ResponseGroup.CatalogEntryFull));



#177862
Edited, Apr 20, 2017 20:32
Vote:
 

Hi

The replacement for CatalogContext is ICatalogSystem. Just get one of those from the service locator (either from the ServiceLocator or using constructor injection) and you can directly replace CatalogContext.Current.

If you want, you can also look into the IContentRepository, to get a more uniform way of working with Commerce and Content.

Regards

Per Gunsarfs

#177873
Apr 21, 2017 10:22
Vote:
 

CatalogContext - or actually ICatalogSystem - is still working fine, but we recommend to have a transition to the content APIs as soon as possible, for various reasons, which were mentioned here http://world.episerver.com/blogs/Quan-Mai/Dates/2014/10/Moving-away-from-ICatalogSystem-part-0/

There is no simple Find-and-replace approach to change from ICatalogSystem to content APIs, but at the 10000 feet view, you'll need to use IContentRepository and the content types (ProductContent, VariationContent, etc. and their inherited classes) to work with catalog contents.
Regards,
/Q

#177875
Apr 21, 2017 10:41
Vote:
 

If you are just doing loading you can start with IContentLoader and wait with IContentRepository for when you actually need to save your changes.

The Content APIs no longer gives you the untyped entries and doesn't use CatalogEntryResponseGroup.

Instead it works with typed classes and a specific language.

The corresponding content code for your calls would be:

var contentLink = _referenceConverter.GetContentLink(code, CatalogContentType.CatalogEntry);
var yourEntry = ContentReference.IsNullOrEmpty(contentLink) ? null : _contentLoader.Get<YourEntryClass>(contentLink, new CultureInfo("en-GB"));

If you want to use any related entities those have to be loaded seperatly, you no longer work with a single object that contains loads of tables.

#177886
Apr 21, 2017 16:34
Vote:
 

Ah..

We used to use "CatalogEntryResponseGroup.ResponseGroup.CatalogEntryInfo" because we believed it was more performant (returning less data).

Now, is that similar to simply using _contentLoader.Get<Entry> because it doesn't automatically load tons of related data?

(or, is there a different class type that I should load?)

#178130
May 02, 2017 17:00
Vote:
 

You should not use Entry, it's an old data class thing. You should be using ProductContent or VariationContent, or even better, their derived classes which represent your model type.

_contentLoader.Get<ProductContent> will load all the data attached to a product (except, well, prices and inventories). If you care about performance, then the general advice is to not load anything if you don't need it. You can find more detail here: http://vimvq1987.com/2017/02/episerver-commerce-performance-optimization-part-1/

#178131
May 02, 2017 17:09
Vote:
 

It was very much more performant with CatalogEntryInfo.

Yes it is very similar to use _contentLoader.Get<T> for that exakt reason.

Recommended way is to define your own class type to load, it should include all your extended meta fields:

[CatalogContentType(GUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx",
        MetaClassName = "ArticleMetaClass",
        DisplayName = "Article",
        Description = "")]
    [GenerateTypeScriptDefinition]
    public class ArticleDataModel : VariationContent,     {
        public virtual string SupplierArticleNumber { get; set; }

        [Searchable, IncludeInDefaultSearch]
        public virtual string EAN { get; set; }

        [CultureSpecific]
        [Display(Name = "Cost price", Order = 170)]
        public virtual decimal CostPrice { get; set; }

       //.....
}
#178132
May 02, 2017 17:10
Vote:
 

Re: performance, the content allows much better cache-hit than the DTO approach, so in short term it can be slower, but in long term, and especially in concurrent scenarios, then content way fares better performance overall.

#178133
May 02, 2017 17:16
Vote:
 

Thank you all,

But now I wonder - when I render an item list page: for each product I display, I retrieve the Product + ALL it's Variants. 

There is certain data that I calculate from the Variants (such as "the price of the least expensive Variant", or "is any variant on sale").

I see that it could be expensive to retrieve all those variants.  Also, ideally, I do not want to cache Variant price, sale status, etc. (or maybe only cache them for a few minutes.  But not longer than that).

Is there a simple way I can use IContentLoader to retrieve only "the price of the least expensive Variant", or "is any variant on sale"? (currently I am retrieving ALL product Variants, then iterating through them locally to calculate these things).

#178135
May 02, 2017 18:32
Vote:
 

There is an interface (IRelationRepository) and its extensions, which you can use to get the variants of a product. From that you can use some service (IPriceService) to load prices, without having to load the content themselves. Also IPromotionEngine extensions allow you you get promotions applied to them.

I would suggest you to explore the APIs, including interfaces and their extension methods. Episerver might already provide something more lightweight and effective to do what you want to, so you won't end up going the long way.

#178140
May 02, 2017 21:00
Vote:
 

"Also, ideally, I do not want to cache Variant price, sale status, etc. (or maybe only cache them for a few minutes.  But not longer than that)"

I understand the sentiment but you can never build a high-performance site on that premise. To be able to deliver good performance on any site everything needed for listings should come from a search engine and even in your case if you want to be able to filter or sort by "the price of the least expensive Variant" or "is any variant on sale" you really need to put those values in your search index.

Keeping the search index updated with current values as soon as something happens to change them is a challenge but it really is the only way i know to achieve anything above sub-par performance.

#178170
May 03, 2017 15:00
Vote:
 

Thanks guys.. Good advice, as always.  I have a lot of things to think about, and to learn about...


I have tried starting with a simple custom class, like this:

[CatalogContentType]
public class MyProduct : ProductContent
{
}


But when I try to "get" one from IContentLoader, I get an exception:

var product = _contentLoader.Get<ProductContent>(contentLink); // <---works!
var product2 = _contentLoader.Get<MyProduct>(contentLink); // <---throws exception!


Exception: Content with id '93169__CatalogContent' is of type 'Castle.Proxies.ProductContentProxy' which does not inherit required type 'Business.Product.MyProduct'


There must be more to it then.. What am I missing?

#178261
May 05, 2017 17:01
Vote:
 

If contentLink is a ProductContent, but not MyProduct, that's expected. You can't get an existing content and cast it to something else like that. You have to create a new MyProduct instance and save it first 

#178262
May 05, 2017 17:04
Vote:
 

What quan says or if you really intend to load an existing class you should set the existing metaclass name in your CatalogContentType.

Either way I would advice you to always explicitly set the Guid so that it will be the same in all environments instead of different random generated identifiers.

#178263
May 05, 2017 17:12
Vote:
 

@Erik, you said -

I understand the sentiment but you can never build a high-performance site on that premise. 
To be able to deliver good performance on any site everything needed for listings should come from a 
search engine and even in your case if you want to be able to filter or sort by "the price of the least 
expensive Variant" or "is any variant on sale" you really need to put those values in your search index.

I am not using Epi Find (at the moment).  But, in the future I will be using it.  Hopefully I can do okay using only Epi repositories for now :(

Thank you all for all your advice.

Here is what my code looks like now:

            // NOT cached!!!
            var productRef = _referenceConverter.GetContentLink(code, CatalogContentType.CatalogEntry);
            
            // cached for a long time:
            var productContent = localCache.GetProduct(productRef) ??
                                     _contentLoader.Get<ProductContentWrapper>(productRef);

            // cached for a long time:
            var variationRefs = _relationRepository.GetRelationsBySource<ProductVariation>(productRef)
                                                   .Select(x => x.Target)
                                                   .Where(x => !ContentReference.IsNullOrEmpty(x));

            // cached for 5 minutes:
            var variationContents = variationRefs.Select(_contentLoader.Get<VariationContent>);

            // cached for 5 minutes:
            var prices = localCache.GetPrices(productRef) ??
                             _priceDetailService.List(productRef); 

Should I cache the call to _referenceConverter.GetContentLink?  (I heard that Epi does not cache this for me).

Also I was wondering, I use IContentLoader because it is read-only.  Are there read-only interfaces to replace IRelationRepository or IPriceDetailService?

Thanks!

 - Ken

#178359
Edited, May 09, 2017 22:38
Vote:
 

If you are using 9.24 or later version then ReferenceConverter.GetContentLink is cached. 

And no, there is no default read-only implementation of IRelationRepository or IPriceDetailService.

The default implementation of IPriceDetailService is not cached. The other interface, IPriceService, is 

#178360
May 10, 2017 1:02
Vote:
 

If you only want to do Read-only operations for price then the other interface that Quan mentioned IPriceService is highly recommended.

IPriceDetailsService is intended to be used for changing the prices or making an admin interface where the user can change them.

For making queries to serve the site with current prices IPriceService is the one to use.

#178386
May 10, 2017 15:08
Vote:
 

Does EPi also cache my call to:

_relationRepository.GetRelationsBySource<ProductVariation>(productRef)

(I was going to cache this locally myself)

#178503
May 12, 2017 0:48
Vote:
 

Currently, no. That will be cached in Commerce 11, however the APIs of IRelationRepository will be changed as well (Source/Target will be obsoleted and replaced by Child/Parent)

#178506
May 12, 2017 7:31
Vote:
 

One more thing, I noticed that ProductContent has a method: GetNodeRelations().

Can I simply use this instead of: _relationRepository.GetRelationsBySource(productReference)? 

Is there any reason not to?

#178713
May 18, 2017 18:56
Vote:
 

There are several extensions methods to help getting relations/associations easier. Note that by using that exact extension method, you will have limited mocking ability. If you want to unit test your code, use the other overload which takes IRelationRepository/ILinkRepository 

#178719
May 19, 2017 1:42
Vote:
 

I found the question a bit ambiguous since you omitted which type of relations you wanted to retrieve.

GetNodeRelations can be used instead of a call to _relationRepository.GetRelationsBySource<NodeRelation>()

but it can't be used to replace the call to

_relationRepository.GetRelationsBySource<ProductVariation>(productRef)

that you used in your code above.

#178739
May 19, 2017 15:11
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.