Gael's profileGael Duhamel - SharePoin...PhotosBlogListsMore Tools Help

Blog


    October 26

    [Commerce Server] ProductConfiguration / CategoryConfiguration ou les oubliés de Commerce Server

    Ces derniers temps, j’ai été sollicité sur des problématiques d’expertise Commerce Server qui m’ont permis de me rendre compte que 2 classes fondamentales pour tous développements Commerce Server étaient totalement méconnue, j’ai nommé:

    Ces deux classes héritent toutes de la classe Microsoft.CommerceServer.Catalog.CatalogItemConfiguration, et elles sont utilisées pour ne charger que les éléments que vous avez besoin d’utiliser. En effet, il n’est pas rare lorsque vous souhaitez afficher une liste de produits, de ne vouloir afficher que certaines propriétés du produit sans avoir à utiliser ses variants, ses catégories parentes, etc., etc....

    D’ailleurs, si vous jetez un œil sur l’API via Reflector, vous vous apercevez que la classe CategoryConfiguration ou ProductConfiguration est instanciée si vous n’en spécifiez aucune (ie, le framework choisit pour vous les choses à charger):

    image image

    Pour illustrer l’importance de ces classes, prenons l’exemple de la récupération d’un produit ou nous n’avons besoin d’afficher que son prix et son descriptif:

    • Sans ProductConfiguration:
    Product product = catalogCtx.GetProduct("Adventure Works Catalog", "AW029-03");
    Console.WriteLine("Pid: " + product.ProductId + " - " + product.DisplayName + " - " + product.ListPrice);

    Grâce au SQL Profiler, nous pouvons récupérer les appels SQL Server et se rendre compte qu’ici 2 procédures stockées sont appelées:

    exec dbo.ctlg_GetProductProperties
         @BCName=N'Adventure Works Catalog'
        ,@fVirtualCatalog=0
        ,@oid=30
        ,@ProductUID_tmp=N'ProductCode'
        ,@VariantUID_tmp=N'VariantCode'
        ,@catalogname=N'Adventure Works Catalog'
        ,@Language=N'en-US'
        ,@eJoinType=-1
        ,@TargetTableName=N''
        ,@SourceJoinKey=N''
        ,@TargetJoinKey=N''
        ,@Columns_tmp=N'P.BaseCatalogName,CategoryName,P.oid,DefinitionName,IsSearchable,cy_list_price,UseCategoryPricing,i_ClassType,CatalogName,ProductId,OrigProductId,VariantId,OrigVariantId,PrimaryParentCategory,DisplayName,Timestamp,OriginalPrice,LastModified,ParentOId'
        ,@EnableInventory=1
        ,@InventoryServerName=NULL
        ,@InventoryDatabaseName=NULL
        ,@FilterOutOfStockProducts=0
        ,@FilterBackOrderableProducts=0
        ,@filterPreorderedSkus=0
        ,@UseThresholdAsFloor=1
        ,@TreatmissingAsOutOfStock=0
        ,@StockHandling=0
    
    declare @p15 int
    set @p15=3
    exec dbo.ctlg_GetProductVariantProperties
        @fVirtualCatalog=0
        ,@catalogname=N'Adventure Works Catalog'
        ,@Language=N'en-US'
        ,@ProductID=N'AW029-03'
        ,@VariantID=N''
        ,@eJoinType=-1
        ,@TargetTableName=N''
        ,@SourceJoinKey=N''
        ,@TargetJoinKey=N''
        ,@BuiltInColumnsToReturn=N'BaseCatalogName,CategoryName,OrigCategoryName,P.oid,DefinitionName,IsSearchable,cy_list_price,UseCategoryPricing,i_ClassType,P.CatalogName,ProductId,OrigProductId,VariantId,OrigVariantId,PrimaryParentCategory,DisplayName,Timestamp,OriginalPrice,LastModified,rank,ParentOId'
        ,@OrderBy=N'[Rank]'
        ,@StartingRec=1
        ,@NumRecords=2147483646
        ,@SortAscending=1
        ,@RecordCount=@p15 output
        ,@ProductOID=30
        ,@EnableInventory=1
        ,@InventoryServerName=default
        ,@InventoryDatabaseName=default
        ,@FilterOutOfStockProducts=0
        ,@FilterBackOrderableProducts=0
        ,@FilterPreorderedSkus=0
        ,@UseThresholdAsFloor=1
        ,@TreatmissingAsOutOfStock=0
        ,@StockHandling=0
        ,@VariantUID=N'VariantCode'
    select @p15
    • Via un ProductConfiguration:
    ProductConfiguration pdtConf = new ProductConfiguration();
    pdtConf.InventoryOptions = new Microsoft.CommerceServer.Inventory.InventoryOptions();
    pdtConf.InventoryOptions.FilterBackorderableSkus = false;
    pdtConf.InventoryOptions.FilterOutOfStockSkus = false;
    pdtConf.InventoryOptions.FilterPreorderableSkus = false;
    pdtConf.LoadAncestorCategories = false;
    pdtConf.LoadCanonicalCategories = false;
    pdtConf.LoadParentCategories = false;
    pdtConf.LoadRelatedCategories = false;
    pdtConf.LoadRelatedProducts = false;
    pdtConf.LoadVariants = false;
    Product product = catalogCtx.GetProduct("Adventure Works Catalog", "AW029-03", "en-US", pdtConf);
    Console.WriteLine("Pid: " + product.ProductId + " - " + product.DisplayName + " - " + product.ListPrice);

    Cette fois-ci nous pouvons constater qu’une seule procédure stockée est appelée (car nous ne remontons pas les variants):

    exec dbo.ctlg_GetProductProperties
         @BCName=N'Adventure Works Catalog'
        ,@fVirtualCatalog=0
        ,@oid=30
        ,@ProductUID_tmp=N'ProductCode'
        ,@VariantUID_tmp=N'VariantCode'
        ,@catalogname=N'Adventure Works Catalog'
        ,@Language=N'en-US'
        ,@eJoinType=-1
        ,@TargetTableName=N''
        ,@SourceJoinKey=N''
        ,@TargetJoinKey=N''
        ,@Columns_tmp=N'P.BaseCatalogName,CategoryName,P.oid,DefinitionName,IsSearchable,cy_list_price,UseCategoryPricing,i_ClassType,CatalogName,ProductId,OrigProductId,VariantId,OrigVariantId,PrimaryParentCategory,DisplayName,Timestamp,OriginalPrice,LastModified,ParentOId'
        ,@EnableInventory=1
        ,@InventoryServerName=NULL
        ,@InventoryDatabaseName=NULL
        ,@FilterOutOfStockProducts=0
        ,@FilterBackOrderableProducts=0
        ,@filterPreorderedSkus=0
        ,@UseThresholdAsFloor=1
        ,@TreatmissingAsOutOfStock=0
        ,@StockHandling=0

    En apparence , le ProductConfiguration nous a permi d’éviter l’appel à la procédure stockée qui nous remontait les variants. Mais jetez un oeil du côté des statistiques SQL pour vous apercevoir qu’il y a bien plus que cela:

    • Sans ProductConfiguration

      Table 'Adventure Works Catalog_CatalogProducts'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'CatalogDefinitionPropertyList'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'CatalogDefinitionPropertyList'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'InventoryCatalogMap'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'Adventure Works Catalog_en-US_Catalog'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'Adventure Works Catalog_CatalogProducts'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'Adventure Works Catalog_CatalogProducts'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'CatalogDefinitionPropertyList'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'CatalogDefinitionPropertyList'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'InventoryCatalogMap'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'CtlInv__Variants1__for_spid__60'. Scan count 0, logical reads 7, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'Adventure Works Catalog_CatalogProducts'. Scan count 3, logical reads 6, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'Adventure Works Catalog_CatalogHierarchy'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'Adventure Works Catalog_en-US_Catalog'. Scan count 0, logical reads 6, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'Adventure Works Catalog_CatalogProducts'. Scan count 3, logical reads 6, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'CtlInv__Variants1__for_spid__60'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    • Avec ProductConfiguration

      Table 'Adventure Works Catalog_CatalogProducts'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'CatalogDefinitionPropertyList'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'CatalogDefinitionPropertyList'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'InventoryCatalogMap'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'Adventure Works Catalog_en-US_Catalog'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
      Table 'Adventure Works Catalog_CatalogProducts'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Impressionnant non? :) 3 fois moins d’exécution côté SQL :)

    Attaquons nous maintenant à la catégorie. C’est là ou les résultats sont les plus impressionnant (si si :))

    • Sans CategoryConfiguration
    Category category = catalogCtx.GetCategory("Adventure Works Catalog", "Tents");
    Console.WriteLine("Cid: " + category .Name+ " - " + category .DisplayName);
    Côté SQL, nous constatons que 3 procédures stockées sont appelées :
    exec dbo.ctlg_GetCategoryProperties
         @BCName=N'Adventure Works Catalog'
        ,@oid=3
        ,@catalogname=N'Adventure Works Catalog'
        ,@Language=N'en-US'
        ,@Columns_tmp=N'BaseCatalogName,CategoryName,OrigCategoryName,oid,DefinitionName,IsSearchable,cy_list_price,UseCategoryPricing,i_ClassType,CatalogName,ProductId,OrigProductId,VariantId,OrigVariantId,PrimaryParentCategory,DisplayName,Timestamp,OriginalPrice,LastModified,ParentOId'
        ,@fVirtualCatalog=0
    
    declare @p13 int
    set @p13=4
    exec dbo.ctlg_GetProductsForCategory
         @CatalogName=N'Adventure Works Catalog'
        ,@language=N'en-US'
        ,@BCName=N'Adventure Works Catalog'
        ,@CategoryOid=3
        ,@ClassType=12
        ,@StartingRecord=1
        ,@NumRecords=500
        ,@OrderBy=N'[Rank]'
        ,@eJoinType=-1
        ,@TargetTableName=N''
        ,@SourceJoinKey=N''
        ,@TargetJoinKey=N''
        ,@RecordCount=@p13 output
        ,@PropertiesToReturn=N'*'
        ,@SortAscending=1
        ,@EnableInventory=1
        ,@InventoryServerName=NULL
        ,@InventoryDatabaseName=NULL
        ,@FilterOutOfStockProducts=0
        ,@FilterBackOrderableProducts=0
        ,@FilterPreOrderableProducts=0
        ,@UseThresholdAsFloor=1
        ,@TreatmissingAsOutOfStock=0
        ,@StockHandling=0
        ,@InventoryPropertiesToReturn=N'*'
        ,@fVirtualCatalog=0
    select @p13
    
    declare @p10 int
    set @p10=0
    exec dbo.ctlg_GetChildCategories
         @CatalogName=N'Adventure Works Catalog'
        ,@Language=N'en-US'
        ,@BCName=N'Adventure Works Catalog'
        ,@oid=3
        ,@StartingRec=1
        ,@NumRecords=500
        ,@PropertiesToReturn=N'*'
        ,@OrderBy=N'[Rank]'
        ,@SortAscending=1
        ,@RecordCount=@p10 output
        ,@fVirtualCatalog=0
    select @p10
    • Avec un CategoryConfiguration: 
    CategoryConfiguration catConf = new CategoryConfiguration();
    catConf.InventoryOptions = new Microsoft.CommerceServer.Inventory.InventoryOptions();
    catConf.InventoryOptions.FilterBackorderableSkus = false;
    catConf.InventoryOptions.FilterOutOfStockSkus = false;
    catConf.InventoryOptions.FilterPreorderableSkus = false;
    catConf.LoadAncestorCategories = false;
    catConf.LoadCanonicalCategories = false;
    catConf.LoadParentCategories = false;
    catConf.LoadRelatedCategories = false;
    catConf.LoadRelatedProducts = false;
    Category category = catalogCtx.GetCategory("Adventure Works Catalog", "Tents", "en-US", catConf);
    Console.WriteLine("Cid: " + category .Name+ " - " + category .DisplayName);

    Côté base de données, plus qu’un seul appel à une procédure stockée:

    exec dbo.ctlg_GetCategoryProperties
         @BCName=N'Adventure Works Catalog'
        ,@oid=3
        ,@catalogname=N'Adventure Works Catalog'
        ,@Language=N'en-US'
        ,@Columns_tmp=N'BaseCatalogName,CategoryName,OrigCategoryName,oid,DefinitionName,IsSearchable,cy_list_price,UseCategoryPricing,i_ClassType,CatalogName,ProductId,OrigProductId,VariantId,OrigVariantId,PrimaryParentCategory,DisplayName,Timestamp,OriginalPrice,LastModified,ParentOId'
        ,@fVirtualCatalog=0

    Grâce à la classe CategoryConfiguration correctement configurée, on a évité 2 exécutions de procédure stockée, mais vu côté statistique SQL c’est impressionnant! Jugé plutôt:

    • Sans CategoryConfiguration

    Table 'Adventure Works Catalog_CatalogProducts'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'CatalogDefinitionPropertyList'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Adventure Works Catalog_en-US_Catalog'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Adventure Works Catalog_CatalogProducts'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'CtlInv__FirstLevelProducts__for_spid__59'. Scan count 0, logical reads 27, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Adventure Works Catalog_CatalogProducts'. Scan count 4, logical reads 8, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Adventure Works Catalog_CatalogHierarchy'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'InventoryCatalogMap'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'CtlInv__SecondLevelProducts__for_spid__59'. Scan count 0, logical reads 18, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'CtlInv__FirstLevelProducts__for_spid__59'. Scan count 5, logical reads 10, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Adventure Works Catalog_en-US_Catalog'. Scan count 0, logical reads 8, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Adventure Works Catalog_CatalogProducts'. Scan count 4, logical reads 8, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'CtlInv__SecondLevelProducts__for_spid__59'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'CtlInv__ChildCategories__for_spid__59'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Adventure Works Catalog_CatalogProducts'. Scan count 4, logical reads 8, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Adventure Works Catalog_CatalogHierarchy'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'CtlInv__ChildCategories__for_spid__59'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    • Avec CategoryConfiguration

    Table 'Adventure Works Catalog_CatalogProducts'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'CatalogDefinitionPropertyList'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Adventure Works Catalog_en-US_Catalog'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Adventure Works Catalog_CatalogProducts'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Quasiment 5 fois moins d’exécution SQL, pour un même résultat!!!!

    Vous comprennez donc maintenant pourquoi il est très important de spécifier un CatalogItemConfiguration: il peut changer drastiquement les performances de votre site e-Commerce (et ceci même si vous utilisez le cache).

    Pour ma part, si vous ne savez pas à l’avance quoi loader (c’est rarement le cas… j’espère ;)), je vous recommande plutôt d’opter pour du lazy loading, c’est à dire de configurer au minima (en gros je mets toutes les options à false) mon GetProduct ou GetCategory et laisse l’API charger les objets dont j’ai besoin.

    Pour terminer, j’ai mis un petit graphique illustrant mes propos ou je me suis amusé à boucler 50 fois sur une récupération de Produit via un getProduct avec différentes options (pour info le cache était activé):

    En bleu:

    ctx.RefreshCache(); //refresh the catalogue cache system
    for (int i = 0; i < 50; i++)
    {
         chrono.Start();
         foreach (String pId in Constants.PRODUCTS)
         {
              Product p = ctx.GetProduct(Constants.CATALOGNAME, pId, "en-US");
         }
         chrono.Stop();
         long spent = chrono.ElapsedMilliseconds;
         chrono.Reset();
    }

    En rouge:

    ctx.RefreshCache(); //refresh the catalogue cache system
    ProductConfiguration pdtConf = new ProductConfiguration();
    pdtConf.InventoryOptions = new Microsoft.CommerceServer.Inventory.InventoryOptions();
    pdtConf.InventoryOptions.FilterBackorderableSkus = true;
    pdtConf.InventoryOptions.FilterOutOfStockSkus = true;
    pdtConf.InventoryOptions.FilterPreorderableSkus = true;
    pdtConf.InventoryOptions.PropertiesToReturn = propertiesToReturn;
    
    pdtConf.LoadAncestorCategories = true;
    pdtConf.LoadCanonicalCategories = true;
    pdtConf.LoadParentCategories = true;
    pdtConf.LoadRelatedCategories = true;
    pdtConf.LoadRelatedProducts = true;
    pdtConf.LoadVariants = true;
    pdtConf.ParentPropertiesToReturn = "*";
    pdtConf.Variants.SearchOptions = new CatalogSearchOptions();
    pdtConf.Variants.SearchOptions.PropertiesToReturn = "*";
    for (int i = 0; i < 50; i++)
    {
         chrono.Start();
         foreach (String pId in Constants.PRODUCTS)
         {
              Product p = ctx.GetProduct(Constants.CATALOGNAME, pId, "en-US", pdtConf);
         }
         chrono.Stop();
         long spent = chrono.ElapsedMilliseconds;
         chrono.Reset();
    }

    En vert:

    ctx.RefreshCache(); //refresh the catalogue cache system
    ProductConfiguration pdtConf = new ProductConfiguration();
    pdtConf.InventoryOptions = new Microsoft.CommerceServer.Inventory.InventoryOptions();
    pdtConf.InventoryOptions.FilterBackorderableSkus = false;
    pdtConf.InventoryOptions.FilterOutOfStockSkus = false;
    pdtConf.InventoryOptions.FilterPreorderableSkus = false;
    pdtConf.InventoryOptions.PropertiesToReturn = propertiesToReturn;
    pdtConf.LoadAncestorCategories = false;
    pdtConf.LoadCanonicalCategories = false;
    pdtConf.LoadParentCategories = false;
    pdtConf.LoadRelatedCategories = false;
    pdtConf.LoadRelatedProducts = false;
    pdtConf.LoadVariants = false;
    pdtConf.ParentPropertiesToReturn = "*";
    pdtConf.Variants.SearchOptions = new CatalogSearchOptions();
    pdtConf.Variants.SearchOptions.PropertiesToReturn = "*";
    
    for (int i = 0; i < 50; i++)
    {
         chrono.Start();
         foreach (String pId in Constants.PRODUCTS)
         {
              Product p = ctx.GetProduct(Constants.CATALOGNAME, pId, "en-US", pdtConf);
         }
         chrono.Stop();
         long spent = chrono.ElapsedMilliseconds;
         chrono.Reset();
    }
    image 

    Comments

    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.

    To add a comment, sign in with your Windows Live ID (if you use Hotmail, Messenger, or Xbox LIVE, you have a Windows Live ID). Sign in


    Don't have a Windows Live ID? Sign up

    Trackbacks

    The trackback URL for this entry is:
    http://gaelduhamel.spaces.live.com/blog/cns!A6E8B6350CC655C7!2012.trak
    Weblogs that reference this entry
    • None