decimal value error with different culture./lanuage

 

  

step="@rangeValue.Step.ToInvariantString()">

Holzpichler
/// Ticket 98400: Wishlist does not adopt/show the attributes and range values
//Convert to decimal with different culters.
range.Value = Decimal.Parse(rangeValue, System.Globalization.CultureInfo.InvariantCulture);







How to get product URL

 @*Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes.*@

var productUrl = Url.Sana.Product(Product, CommerceFrameworkBase.Context.LanguageId);

Add new Section to Admin Product settings / Navigation search new tab/

 Eg : medux, Kavo 


Update AdminMenuInitializer

layoutTabs.Add(AdminMenuConstants.LayoutTabs.ProductSettings, new TabInfoCollection()
{
    CreateLayoutTab("productsets"T.ProductSets, urlHelper.Action("Index""ProductSets"),
        PermissionOn.ProductSets),
    CreateLayoutTab("price"T.Price, urlHelper.Action("Price""ProductSettings"),
        PermissionOn.ProductSettings),
    CreateLayoutTab("specs"T.ProductSpecifications, urlHelper.Action("Index""ProductSpecs"),
        PermissionOn.ProductSettings),
    CreateLayoutTab("stock"T.StockAndAssortment, urlHelper.Action("StockAndAssortment""ProductSettings"),
        PermissionOn.ProductSettings),
    CreateLayoutTab("stockranges"T.StockLevels, urlHelper.Action("Index""StockRanges"),
        PermissionOn.ProductSettings),
    CreateLayoutTab("uoms"T.UnitsOfMeasure, urlHelper.Action("UnitsOfMeasure""ProductSettings"),
        PermissionOn.ProductSettings),
    CreateLayoutTab("relatedproducts"Admin.ResourceManager.GetString(Sana.Commerce.Constants.RelatedProductSettingsMenu), urlHelper.Action("RelatedProductSetting""ProductSettings"),
        PermissionOn.ProductSettings), <------
});


update  ExtendedProductSettingsController

[HttpGet]
        [ErpConnectionRequired]
        public virtual ActionResult RelatedProductSetting()
        {
            var model = ObjectFactory.Create<RelatedProductModel>();
            model.Initialize(Shop.Settings);
            return View(model);
        }
 
       /// <summary>
        /// A POST action to save the stock and assortment settings.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <returns>Returns a view result.</returns>
        [HttpPost]
        [ValidateInput(false)]
        [SanaValidateAntiForgeryToken]
        public virtual ActionResult RelatedProductSetting(RelatedProductModel model)
        {
            if (ModelState.IsValid)
            {
                if (DataManager.ChangeWebsiteSettings(model.ApplyChanges))
                    RefreshSettingsCache();
            }
            return View(model);
        }






// Ticket 100006: [Topmedia] 3.1. Search – Product Finde
AdminMenuInitializer

protected virtual MenuGroupCollection CreateMainMenu(AdminUrlHelper urlHelper, string erpName)
        {
            var mainMenuItems = ObjectFactory.Create<MenuGroupCollection>();

            mainMenuItems.Add(
                  ............
                        CreateAdminMenuLink(T.SearchAndResults, urlHelper.Action("SearchSettings""ProductIndex"),
                            PermissionOn.SearchSettings),

                    CreateAdminMenuLink(Admin.ResourceManager.GetString(Sana.Commerce.Constants.ProductFinder), urlHelper.Action("ProductFinder""ProductIndex"),
                            PermissionOn.SearchSettings)),

AdminMenuInitializer
layoutTabs.Add(AdminMenuConstants.LayoutTabs.NavigationAndSearch, new TabInfoCollection()
            {
                CreateLayoutTab("menus", T.Menus, urlHelper.Action("Index""Menus"), PermissionOn.NavigationSettings),
                CreateLayoutTab("facets", T.FacettedFilters, urlHelper.Action("FacetsSettings""ProductIndex"),
                    PermissionOn.FacettedFilters),
                CreateLayoutTab("search", T.SearchAndResults, urlHelper.Action("SearchSettings""ProductIndex"),
                    PermissionOn.SearchSettings),
                // Ticket 100006: [Topmedia] 3.1. Search – Product Finder
                CreateLayoutTab("productfinder", T.ResourceManager.GetString("ProductFinder"), urlHelper.Action("ProductFinderSettings""ProductIndex"),
                    PermissionOn.FacettedFilters),
            });




How to compair two lines in SANA / Linq list

 

Project Bosig,

 Jimlorance. : https://sanacommerce.visualstudio.com/Sana%20Projects/_git/JimLawrence_932?path=%2FSana.Commerce.Sdk%2FCustomization%2FShop%2FExtendedSalesLineComparer.cs&_a=contents&version=GBmaster

HolzPicher

// Ticket 93818: [Holz Pichler] 3.3.Item variants – “range value” attributes.
public class ExtendedSalesLineComparer : SalesLineComparer
    {
        public override bool MatchSku(ISalesLine line1ISalesLine line2)
        {
            var baseResult = base.MatchSku(line1line2);
            var result =  baseResult && CompareRangeValues(line1line2);
            return result;
        }
 
        protected virtual bool CompareRangeValues(ISalesLine line1ISalesLine line2)
        {
            // Ticket 93818: [Holz Pichler] 3.3.Item variants – “range value” attributes.
 
            var excictingLineRangeValues = ((BasketLine)line1).RangeValueList;
            var newLineRangeValues = ((BasketLine)line2).RangeValueList;
 
            var machingRangeValueFields = excictingLineRangeValues.Where(x => newLineRangeValues.Any(y => x.Name == y.Name && x.Id == y.Id && x.Value == y.Value)).ToList();
            var result  =  (machingRangeValueFields.Count == newLineRangeValues.Count);
            return result;
 
        }
 
 
         
 
 
 
    }



                latestMissmatchList = latestJsonList.Where(s => !oldCJsonList.Any(s2 => s2.Value.Contains(s.Value) && s2.Key.Contains(s.Key))).ToList();

                oldMissmatchList = oldCJsonList.Where(s => !latestJsonList.Any(s2 => s2.Value.Contains(s.Value) && s2.Key.Contains(s.Key))).ToList();


var l1 = currentList.Where(m => !oldList.Contains(m)).ToList();

            var l2 = oldList.Where(m => !currentList.Contains(m)).ToList();


            var diff1 = currentList.Except(oldList).ToList();

            var diff2 = oldList.Except(currentList).ToList();









Quick order / knowkout add variable to quick order

 Project Holzpitcher

@* Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. *@

control.quickorder.js

//Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes.

        self.isVarient = ko.observable(false); //Product contain varients or not        


function QuickOrderViewModel() {        

        var self = this;
 
           .....
 
        //Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes.
        self.isVarient = ko.observable(); //Product contain varients or not
 
        .....




var _buildComponentGroups = function (data) {
           var componentsCollection = data.VariantComponents,
               variantsCollection = data.Variants;
 
           if (componentsCollection.length) {
 
               //Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes.
               if (componentsCollection.length > 0) {
                   self.isVarient(true)                   
               }
               else {
                   self.isVarient(false)                     
               }
 
               // Product with variants
               self.componentGroups = $.map(componentsCollection, function (vali) {

on view 


get false value 

data-bind="visible: (isVarient() == false)">

get True Value : 

data-bind="visible:isVarient
@* Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. *@
<div class="" data-bind="visible:isVarient">
    <a data-bind="attr: { href: product().url }" data-product-url class="btn"> @Sana.SimpleText("QuiclOrder_SelectVariants""Select Variants"</a>
</div> 
 
@* Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. *@
<div class="qo-quantity-box" data-bind="visible: (isVarient() == false)">
    <!-- ko if: !product().isProductConfigurable -->
    @if (Shop.UserAbilities.Has(AbilityTo.ViewUnitOfMeasure))
    {
        if (Shop.Settings.AllowUnitOfMeasureSelection)


Ravensburg 



control.Quickorder.js



// Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart. 
        self.availabilityHtmlTags = ko.observable();       


function QuickOrderViewModel() {
       var self = this;
 
       ...
 
       self.product = ko.observable();
       self.components = ko.observable();
       self.quantity = ko.observable();
       self.selectedUom = ko.observable();
       self.defaultUomTitle = ko.observable();
       self.quantityStep = ko.observable(1);
       self.minimumQuantity = ko.observable();
       self.maximumQuantity = ko.observable();
        
       // Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart. 
       self.availabilityHtmlTags = ko.observable();       


var _buildComponentGroups = function (data) {
            var componentsCollection = data.VariantComponents,
                variantsCollection = data.Variants;
 
            //Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart
            self.availabilityHtmlTags = data.AvailabilityHtmlTags;
             
            if (componentsCollection.length) {


_quickorder.js

<form data-bind="form: 'quickOrderForm', submit: quickOrderSubmitForm, visible: product()" class="choose-product" style="displaynone;">
        <div data-bind="attr: { 'data-tracking-data': JSON.stringify(product().trackingData) }" class="qo-product-title-box">
            <a data-bind="text: product().title, attr: { href: product().url }" data-product-url class="hyp-qo-title font-darkest" tabindex="-1" target="_blank"></a>
        </div>
 
        <div class="label">
            <label>@Sana.SimpleText("Availability")</label>            
 
            <div>
                @* //Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart *@
                <div data-bind="html:availabilityHtmlTags"></div>
            </div>
 
 
        </div>


using Sana.Commerce.Catalog;
using Sana.Commerce.Customization.Catalog;
using Sana.Commerce.Shop;
using Sana.Commerce.Stock;
using Sana.Commerce.Web;
using Sana.Commerce.Web.Frontend;
using Sana.Commerce.Web.Frontend.Models.Product;
using Sana.Commerce.Web.Frontend.Models.QuickOrder;
using Sana.Commerce.Web.Mvc.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
 
namespace Sana.Commerce.Customization.ExtendedModels
{
    public class ExtendedProductInfoModel : ProductInfoModel
    {
        public StockModel Stock { getset; }
 
        public bool IsInStock { getset; }
 
        public string AvailabilityHref { getset; }
 
        public int MaxStock { getset; }
 
        public string AvailabilityHtmlTags { getset; }
 
 
        public override void Initialize(IProduct product, IWebsiteSettings settingsstring detailsUrlbool isProductConfigurable = false)
        {
            base.Initialize(product, settings, detailsUrl, isProductConfigurable);
            Stock = ((ExtendedProductManager)CommerceFramework.Products).CreateProductStockModel(product);
 
 
            var isInStock = Stock.Inventory > Stock.StockRange.OutOfStock;
            IsInStock = isInStock;
 
            var href = "http://schema.org/" + (isInStock ? "InStock" : "OutOfStock");
           // AvailabilityHref = href;
 
 
            var Model = Stock;
 
 
           var maxStock = Model.StockRange.MaxStockNumber.HasValue ? Model.StockRange.MaxStockNumber.Value : -1;
            MaxStock = maxStock;
 
            //HtmlString htmlString = new HtmlString("");
            StringBuilder buildHtml = new StringBuilder();
 
 
            var ranges = new[]
            {
                new { Min = Int32.MinValue, Max = Model.StockRange.OutOfStock },
                new { Min = Model.StockRange.OutOfStock, Max = Model.StockRange.LowStock },
                new { Min = Model.StockRange.LowStock, Max = Int32.MaxValue },
            };
            foreach (var range in ranges)
            {
                var show = Model.Inventory > range.Min && Model.Inventory <= range.Max;
 
                // var htmlString = new HtmlString();
 
                var stockText_MaxStockNumber = CommerceFrameworkBase.SanaTexts.GetSanaText("StockText_MaxStockNumber").FormatWith(maxStock.ToString());
 
                var isHideClass = (!show) ? "hide" : "";
 
                var strHead = "<span class='stock-row "+ isHideClass  + "'" + 
                 " data-min=" + range.Min.ToInvariantString() + " " +
                 " data-max=" + range.Max.ToInvariantString() + " " +
                 " data-max-stock=" + maxStock + " " +
                 " data-custom-stock-message=" + "'"+  stockText_MaxStockNumber + "'" + " "+
                 ">"; 
                
 
                var strBody = StockAmountExtention(Model.StockRange, show ? Model.Inventory : range.Max);
 
                //Sana.StockAmount(Model.StockRange, show ? Model.Inventory : range.Max)
                //ProductExtensions
                var strFooter = "</span>";
 
                var combinedString = strHead + strBody + strFooter;
                buildHtml.Append(combinedString);
                 
            }
            string sHtml = buildHtml.ToString();
 
             sHtml = sHtml.Replace("\"","'"); // REmove double qu
 
            //var htmlString = new HtmlString(sHtml);
            //AvailabilityHtmlTags = htmlString;
            AvailabilityHtmlTags = sHtml;
            AvailabilityHref = "<p>Trrrrrrr</p>";
 
        }
 
        private string StockAmountExtention(IStockRange stockRangedecimal inventorystring textKeySuffix = null)
        {            
            var val = "";
 
            var textKey = "StockText_";
            if (inventory <= stockRange.OutOfStock)
                textKey += "OutOfStock";
            else if (inventory > stockRange.LowStock)
                textKey += "InStock";
            else
                textKey += "LowStock";
 
            if (ShopContext.GetCurrent().IsB2cCustomer)
                textKey += "_B2C";
            else
                textKey += "_B2B";
 
            if (textKeySuffix != null && !textKeySuffix.StartsWith("_"))
                textKeySuffix = '_' + textKeySuffix;
 
            var inventoryElement = new TagBuilder("span");
            inventoryElement.AddCssClass("stock-amount");
 
            if (stockRange.MaxStockNumber.HasValue && stockRange.MaxStockNumber.Value < inventory)
            {
                var maxStockNumberText = CommerceFrameworkBase.SanaTexts.GetSanaText("StockText_MaxStockNumber").FormatWith(stockRange.MaxStockNumber.Value.ToString());
                inventoryElement.SetInnerText(HttpUtility.HtmlDecode(maxStockNumberText.ToString()));
            }
            else
            {
                inventoryElement.SetInnerText(inventory.ToString());
            }
 
            var txtKeyString = CommerceFrameworkBase.SanaTexts.GetSanaText(textKey + textKeySuffix); //.iReplace("[STOCKAMOUNT]", inventoryElement.AsHtml());
            var htmlKeyString = new HtmlString(txtKeyString);
            var stockText = htmlKeyString.iReplace("[STOCKAMOUNT]", inventoryElement.AsHtml());
 
            val =  StockAmountHelper(stockRange , inventory, stockText);
 
 
            //var stockText = viewHelper.RichText(textKey + textKeySuffix).iReplace("[STOCKAMOUNT]", inventoryElement.AsHtml());
            //return CompiledHelpers.Themed(ProductHelpers.StockAmount, stockRange, inventory, stockText);
 
            //--------
            return val;
        }              
 
 
 
        public string StockAmountHelper(Sana.Commerce.Stock.IStockRange stockRangedecimal inventory, IHtmlString stockText)
        {
            // StockAmount(Sana.Commerce.Stock.IStockRange stockRange, decimal inventory, IHtmlString stockText)
 
            var cssStockState = "low-stock";
            if (inventory <= stockRange.OutOfStock)
            {
                cssStockState = "out-stock";
            }
            else if (inventory > stockRange.LowStock)
            {
                cssStockState = "in-stock";
            }
            var value = "<span class='lbl-stock " + cssStockState + "'> " + stockText + " </span>";
            return value;
 
        }
    }
 
 
 
 
}
 





Search without special characters. Persister /Suggestion Persister/Facet

  Ticket 88715: [Kavo] 3.15. SEARCH – PUNCTUATION MARKS

Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS

bachofn projects.


when the product index run 

it will Hit ExtractKeyword method on LucenIndexManager

public class ExtendedLuceneIndexManager : LuceneIndexManager
    {
         
 
        protected override IDictionary<stringstring> ExtractKeywords(IndexEntry entry)
        {
            var keywords = new Dictionary<stringstring>();

            foreach (var language in languageList)
            {
                var sb = new StringBuilder();
                foreach (var field in indexInfo.KeywordFields) <----from Admin section
                {
                    var persister = IndexFieldPersisterFactory.Current.GetPersister(field.Type);
                    if (!persister.IsSearchable)
                        throw new SanaBusinessException(String.Format("Field '{0}' cannot be a keyword field because the persistor does not allow this field to be searchable.", field.Name));
                    var value = persister.ExtractKeywords(entry.Entity, field, language);
                    sb.Append(value);
                    sb.Append(' ');
                }
                keywords.Add(language, sb.ToString());
            }

            return keywords;
        }
    }

to run it correctly you need to set Keyword fields. 



KeyWord .

can find all from Framework initializer

field No --> IdPersister / ExtendedIdPersister

Title --> TitlePersister

Description -->  ExtendedDescriptionPersister

etc ....

FrameworkInitializer

// Custom product persisters
            factory.Register(ProductIndexFieldTypes.Id, new ExtendedIdPersister());
            factory.Register(ProductIndexFieldTypes.Title, new ExtendedTitlePersister());
            factory.Register(ProductIndexFieldTypes.Price, new PricePersister());
            factory.Register(ProductIndexFieldTypes.VariantTitle, new VariantTitlePersister());
            factory.Register(ProductIndexFieldTypes.ProductCategory, new CategoryPersister());
            factory.Register(ProductIndexFieldTypes.VariantComponent, new VariantComponentPersister());
            factory.Register(ProductIndexFieldTypes.Description, new ExtendedDescriptionPersister());
            factory.Register(ProductIndexFieldTypes.OrderableProductGrossWeight, new OrderableProductGrossWeightPersister());
            factory.Register(ProductIndexFieldTypes.BarCode, new BarCodePersister());
            factory.Register(ProductIndexFieldTypes.Suggestion, new SuggestionPersister());


customization  for the issue, and run the Product index

public class ExtendedIdPersister : IdPersister
    {
        public override string ExtractKeywords(IEntity entity, IndexFieldInfo field, string language)
        { 
 
            //Ticket 88715: [Kavo] 3.15. SEARCH – PUNCTUATION MARKS.
 
            var keywords = base.ExtractKeywords(entity, field, language);
            //To accomodate the keworks without some character set
            var removableSeparators = new string[] { " "",""-""/" };
 
            var config = ConfigurationManager.AppSettings["RemovableSeparatorsForIndexFieldDescription"];
            if (!config.IsEmptyString())
                removableSeparators = config.Split(";").ToArray();
 
            var newKeywords = keywords;
 
            if (!keywords.IsEmptyString())
            {
                foreach (var separator in removableSeparators)
                {
                    if (newKeywords.Contains(separator))
                    {
                        newKeywords = newKeywords.Replace(separator, "");
                    }
                }
                keywords = keywords + " " + newKeywords;
            }
            return keywords;
        }
    }

public class ExtendedIdPersister : IdPersister
    {
        public override string ExtractKeywords(IEntity entityIndexFieldInfo fieldstring language)
        { 
            //Ticket 88715: [Kavo] 3.15. SEARCH – PUNCTUATION MARKS.                        
            var keywords = base.ExtractKeywords(entityfieldlanguage);
            var keywordWithooutSpecialCaracters = ExtendedProductSearchManager.ExtractKeywordwithSpecialCaracters(keywords);
            keywords += " " + keywordWithooutSpecialCaracters;
            return keywords;
        }
    }

public class ExtendedProductSearchManager : ProductSearchManager<ExtendedProductSearchIndexProvider>
    {
        /// <summary>
        /// This method will extract kewords
        /// </summary>
        /// <param name="keywords"></param>
        /// <returns></returns>
        public static string ExtractKeywordwithSpecialCaracters(string keywords)
        {
            //To accomodate the kewords without some character set
            var removableSeparators = new string[] { "-" };
 
            var config = ConfigurationManager.AppSettings["RemovableSeparatorsForIndexFieldDescription"];
            if (!config.IsEmptyString())
                removableSeparators = config.Split(";").ToArray();
 
            var newKeywords = keywords;
 
            if (!keywords.IsEmptyString())
            {
                foreach (var separator in removableSeparators)
                {
                    if (newKeywords.Contains(separator))
                    {
                        newKeywords = newKeywords.Replace(separator"");
                    }
                }                
            }
            return newKeywords;
        }
    }



Quick search on basket page use : SuggestionPersister.

public class ExtendedSuggestionPersister : SuggestionPersister
    {
        /// Ticket 88715: [Kavo] 3.15. SEARCH – PUNCTUATION MARKS.
        /// <summary>
        /// Extracts the keywords from the field for the specified language.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="field">The field.</param>
        /// <param name="language">The language.</param>
        /// <returns>Extracted keywords.</returns>
        public override string ExtractKeywords(IEntity entity, IndexFieldInfo field, string language)
        {
            var keywords = base.ExtractKeywords(entity, field, language);           
            keywords = ExtendedProductSearchManager.ExtractKeywordwithSpecialCaracters(keywords);            
            return keywords;
        }       
    }


This method helps : When user add Keyword Fields

public class ExtendedLuceneIndexManager  : LuceneIndexManager
    {
        protected override IDictionary<stringstring> ExtractKeywords(IndexEntry entry)
        {
            var keywords = new Dictionary<stringstring>();

            foreach (var language in languageList)
            {
                var sb = new StringBuilder();
                foreach (var field in indexInfo.KeywordFields)
                {
                    var persister = IndexFieldPersisterFactory.Current.GetPersister(field.Type);
                    if (!persister.IsSearchable)
                        throw new SanaBusinessException(String.Format("Field '{0}' cannot be a keyword field because the persistor does not allow this field to be searchable.", field.Name));
                    var value = persister.ExtractKeywords(entry.Entity, field, language);
                    sb.Append(value);
                    sb.Append(' ');
                }
                keywords.Add(language, sb.ToString());
            }

            return keywords;
        }        
        
    }

 it will call all the Persisters one by one .

This method helps : When user add FilterFields.

public class ExtendedLuceneIndexManager  : LuceneIndexManager    

protected virtual void AddFilterFields(Document doc, IndexEntry entry)
        {
            foreach (var field in indexInfo.FilterFields)
            {
                var persister = IndexFieldPersisterFactory.Current.GetPersister(field.Type);
                var index = persister.IsSortable && !persister.IsFilterable && !persister.IsSearchable ? Field.Index.NOT_ANALYZED : Field.Index.ANALYZED;
                var store = persister.IsRetrievable || persister.IsSortable ? Field.Store.YES : Field.Store.NO;
                var pairList = persister.GetValuesToIndex(entry.Entity, field).ToList();
                foreach (var pair in pairList)
                {
                    var strValue = persister.GetStringValue(pair.Value);
                    doc.Add(new Field(pair.Name, strValue, store, index));
                }
            }
        }



If string contains special caracter and you want to index those separately 

eg : Name1;Name3;name3; name4 

public class ExtendedStringFieldPersister : StringFieldPersister
    {
        protected override object GetPropertyValue(IEntity entitystring propertyName)
        {
            var pi = entity.GetType().GetProperty(propertyName);
            if (pi != null)
                return pi.GetValue(entitynull);
            
            // Ticket 100006: [Topmedia] 3.1. Search – Product Finder 
            // When split text by Product attribute split key.
            var value = entity.Fields.GetValue(propertyName).SafeToString();
            if (entity is Product && value.iContains(Constants.ProductAttributeSplitKey))
            {
                var val =  value.Split(Constants.ProductAttributeSplitKey).Aggregate((xy=> string.Join(Environment.NewLine, xy));
                return val;
            }
            return value;
        }
    }


public static class Constants

    {
        /// Ticket 100006: [Topmedia] 3.1. Search – Product Finder
        /// <summary>
        /// Product finder settings menu
        /// </summary>          
        public const string ProductAttributeSplitKey = ";";
    }


create new Persister 

/// Ticket 100006: [Topmedia] 3.1. Search – Product Finder
    /// <summary>
    /// Product Type Persister
    /// </summary>
    public class ProductTypePersister : StringFieldPersisterICustomIndexFieldPersister
    {
        /// <summary>
        /// Gets a value indicating whether the field can be searchable by keywords.
        /// </summary>
        /// <value>
        /// If not overridden in a derived class, returns <c>true</c>.
        /// </value>
        public override bool IsSearchable
        {
            get { return true; }
        }
 
        /// <summary>
        /// Gets a value indicating whether the field can be filterable.
        /// </summary>
        /// <value>
        /// If not overridden in a derived class, returns <c>false</c>.
        /// </value>
        public override bool IsFilterable
        {
            get { return true; }
        }
 
        /// <summary>
        /// Gets a value indicating whether the field can be sortable.
        /// </summary>
        /// <value>
        /// If not overridden in a derived class, returns <c>false</c>.
        /// </value>
        public override bool IsSortable
        {
            get { return false; }
        }
 
        /// <summary>
        /// Gets a value indicating whether the field should be retrievable from the index.
        /// </summary>
        /// <value>
        /// If not overridden in a derived class, returns <c>true</c>.
        /// </value>
        public override bool IsRetrievable
        {
            get { return true; }
        }
 
        /// Ticket 100006: [Topmedia] 3.1. Search – Product Finder
        public override IEnumerable<NameValuePairGetValuesToIndex(IEntity entityIndexFieldInfo field)
        {
            var productTypeCode = ((Product)entity).ProductTypeCode;
            var values = productTypeCode != null ? productTypeCode : string.Empty;
            yield return new NameValuePair(Constants.ProductTypeCode, values);
 
        }
 
        /// Ticket 100006: [Topmedia] 3.1. Search – Product Finder
        public override string ExtractKeywords(IEntity entityIndexFieldInfo fieldstring language)
        {
            var productTypeCode = ((Product)entity).ProductTypeCode;
            var values = productTypeCode != null ? productTypeCode : string.Empty;
            return values;
        }
 
 
        public IList<IFieldGetAvailableFields(string fieldType)
        {
            return null;
        }
 
        public void SetupIndexingLoadOptions(IEntityListLoadOptions loadOptionsIndexFieldInfo fieldIIndexInfo indexInfo)
        {
 
        }


Kavo Project

OemReferace are string list eg : 1000,1001,200,2005, .. 

then save as new line  

oemReferenceNumbers.JoinWith(Environment.NewLine)

public class OemReferenceNumbersPersister : StringFieldPersisterICustomIndexFieldPersister
    {
       ...
 
        /// Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS       
        public override IEnumerable<NameValuePairGetValuesToIndex(IEntity entityIndexFieldInfo field)
        {
            var oemReferenceNumbers = ((Product)entity).OemReferenceNumbers;
            var values = oemReferenceNumbers != null ? oemReferenceNumbers.JoinWith(Environment.NewLine) : string.Empty;
            yield return new NameValuePair(Constants.OemReferenceNumbers, values);
 
        }
 
        /// Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS
        public override string ExtractKeywords(IEntity entityIndexFieldInfo fieldstring language)
        {  
            var oemReferenceNumbers = ((Product)entity).OemReferenceNumbers;
            var values = oemReferenceNumbers != null ? oemReferenceNumbers.JoinWith(Environment.NewLine) : string.Empty;
            return values;
 
        } 
        ...


Compititor is a object 

get nessary field only and create a list and bind

var competitorsRefList = competitors?.Select(x => x.Reference).ToList(); 
var values = competitorsRefList != null ? competitorsRefList.JoinWith(Environment.NewLine)


public class CompetitorsPersister : StringFieldPersisterICustomIndexFieldPersister
    {        
       ......
        /// Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS
        public override IEnumerable<NameValuePairGetValuesToIndex(IEntity entityIndexFieldInfo field)
        {
            var competitors = ((Product)entity).Competitors;
            var competitorsRefList = competitors?.Select(x => x.Reference).ToList();
 
            var values = competitorsRefList != null ? competitorsRefList.JoinWith(Environment.NewLine) : string.Empty;
            yield return new NameValuePair(Constants.Competitors, values);
        }
 
        /// Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS
        public override string ExtractKeywords(IEntity entityIndexFieldInfo fieldstring language)
        {
            var competitors = ((Product)entity).Competitors;
            var competitorsRefList = competitors?.Select(x => x.Reference).ToList();
 
            var values = competitorsRefList != null ? competitorsRefList.JoinWith(Environment.NewLine) : string.Empty;
            return values;  
        } 
        .....
    }


register under custom product persisters

Frameworkinitializer.cs

// Ticket 96724: [Kavo] 3.16 SEARCH – OEM# AND COMPETITOR# FIELDS
 factory.Register(Constants.CompetitorsPersisterName, new CompetitorsPersister());
 factory.Register(Constants.OemReferenceNumbersPersisterName, new OemReferenceNumbersPersister());