Axa ouvre ses portes au webcenter de Marcq en baroeul (lille)

Axa organise un Afterwork Portes Ouvertes le jeudi 23 juin prochain sur le site du WebCenter de Marcq en baroeul (Lille) de 18H à 21H au 839 avenue de la république Marcq en baroeul, vous êtes bien sûr très chaleureusement conviés à cet événement, au programme présentation de nos nouveaux locaux, démonstration de nos outils et mise en avant des technologies :
- Silverlight
- Sharepoint
- MVC 3.0
- La mobilité

Il y aura aussi une intervention winwise, un point sur la méthodologie Agile utilisée, un Tournois de Kinnect/Baby Foot et un buffet pour les gourmands.

N’hésitez pas à relayer largement cette invitation à votre entourage, votre réseau d’école ainsi qu’à toute personne intéressée ou évoluant dans les Systèmes Information en leur transférant cet e-mail. Un simple clic sur le lien service.afterwork23juin2011@axa.fr permet de s’inscrire.

Pour info Axa cherche encore des profils ingénieurs développeur/Scrum master (chef de projet) qualifiés en web sur les technos .net :
http://www.axa.fr/Pages/webcenter/axa-lille.htm

Entity Framework utiliser une procédure stockée

Pour des raisons de performance il peut être utile d’utiliser des procédures stockées dans entity framework (par exemple si on connait l’id de l’entité et que l’on veut mettre à jour son contenu sans remonter l’entité).

Pour notre exemple, voici un script qui ajoute une table ‘Contenu’ et une procédure stockée ‘MajContenu’ qui met à jour le contenu selon l’id donné.
Cette procédure stockée renvoie le nombre de lignes mise à jour.
Dans le code si aucune ou plus d’une ligne a été mise à jour on renvoie une exception.

Création de la table Contenu :

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Contenu](
	[IdContenu] [uniqueidentifier] NOT NULL,
	[Contenu] [nvarchar](max) NOT NULL,
 CONSTRAINT [PK_Contenu] PRIMARY KEY CLUSTERED
(
	[IdContenu] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Contenu] ADD  CONSTRAINT [DF_Contenu_IdContenu]  DEFAULT (newid()) FOR [IdContenu]
GO

Création de la procédure stockée MajContenu:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[MajContenu](
	-- Add the parameters for the stored procedure here,
	@IdContenu uniqueidentifier,
	@Contenu nvarchar(max))

AS
BEGIN
SET NOCOUNT ON;
	update Contenu set Contenu = @Contenu
	where IdContenu = @IdContenu;
	select @@RowCount;
END
GO

Clic droit sur l’edmx, mettre à jour le modéle à partir de la base de données et ajoutez la table Contenu et la procédure stockée MajContenu :

Seul la table apparait, pour utiliser la procédure stockée il faut l’ajouter à l’entity container.
Il faut accéder à l’explorateur de modele, clic droit sur l’edmx ‘Explorateur de modele’ :

Comment accéder à l'explorateur de modele

Recherchez la procédure stockée et ajoutez une importation de fonction :

Importe une procédure stockée à l'entité grâce à l'importation de fonction

Reste à choisir un type de retour de la collection, on peut choisir les types classiques entier/string, on peut aussi choisir de retourner une entité ou un type complexe.
Dans notre cas, notre procédure stockée retourne un entier:

Choisir le type de retour de la procedure stockée

Reste à utiliser la procédure stockée dans notre code (ce qui est beaucoup plus facile qu’avec un sqlcommand) :

public void MajContenu(string contenu, Guid contenuId)
        {
            using (testEntities entity = new testEntities())
            {
                int? resu = entity.MajContenu(contenuId, contenu).FirstOrDefault();
                if (!resu.HasValue || resu.Value != 1)
                {
                    throw new Exception("Erreur la procédure stockée MajContenu a modifié " + resu.Value + " enregistrements pour l'id " + contenuId);
                }
            }
        }

Utiliser Entity Framework est dans ce cas un vrai délice intuitif, Facile à utiliser et à comprendre, difficile de s’en passer. Enjoy…

le point d’arret ne passe pas dans l’application silverlight

Aujourd’hui surprise, les points d’arret ne passaient plus dans mon application silverlight, ne pas s’énerver rester calme.
Et effectivement dans le Hoster : Click droit sur le projet web, propriétés, Web, en bas dans la liste des debogueurs la case silverlight était décochée…

Si vous avez des problèmes alors que la case est cochée, décochez là, sauvegardez, re-cochez là et réexecutez.

Double load d’une même page à cause des img src vide

Aujourd’hui j’ai encore eu affaire à un double load d’une même page à chaque accés sur cette page, la cause de ce double load :

<asp:Image runat="server" /> ou <asp:ImageButton runat="server">

qui se transforme en :

<img src="" />


Pourquoi ces dysfonctionnements? D’aprés les normes rfc, les navigateurs doivent remplacer les urls vides des images par l’url de base : http://www.ietf.org/rfc/rfc1808.txt, Section 4, Step 2, a).

Là où ça se complique, les navigateurs ont des comportements différents quand ils rencontrent img src=''
- Quand on interroge ie si l’image est placée sur une url de type ‘http://toto.com/toto/mapage.html’ la requete sera effectuée sur le repertoire dans lequel la page est executée : ‘http://toto.com/toto/’
- Safari et chrome font une requête sur la page en cours
- Opera ne fait aucune requête
- Firefox 3 et avant ont le même comportement que Safari et Chrome, Firefox 3.5 ignore comme Opéra la requête.

Le double postback entraine une surcharge de requêtes inutiles pour le serveur, on passe ainsi d’1 page visitée à 2, de 10000 à 20000, de plus l’internaute réexecute une requete serveur et doit attendre le chargement de l’image pour voir sa page.
Enfin même si la requête ne renvoie pas d’affichage, les cookies et autres informations du header sont quand même envoyés à l’internaute, ce qui peut provoquer pas mal de dysfonctionnements.

Donc sur un lien pas de probleme il faut cliquer dessus pour éxecuter la page, mais sur une image la page est réexecuter pour afficher le résultat. même si l’image est cachée via javascript (dans un div en display none) le code est quand même réexecuté. Le probleme concerne les images et les balises :

<script  scr='' />
<link href='' />
<input type='image' src='' />

concernant ces balises, les navigateurs ont aussi des comportements différents :
- pour la balise input type=’image’ les navigateurs ont le même comportement que pour la balise img
- pour les balise script et link, seul firefox 3 et avant, Safari et Chrome font une requête, les autres ne prennent pas la balise en compte.

Pour remédier au probleme, la solution la plus simple est de pointer par défaut sur une image transparente d’1pixel sur 1 pixel

Attention aussi aux controles externes comme l’AJAX DropDown extender si vous n’utilisez pas l’attribut DropArrowImageUrl alors il génére un src vide pensez du coup à y mettre l’image vide…

Bon courage.

Sources :
http://www.nczonline.net/blog/2009/11/30/empty-image-src-can-destroy-your-site/
http://geekswithblogs.net/bcaraway/archive/2007/08/24/114945.aspx

Pagination avec sql server 2011, implémentation du limit en sql server

Bonjour, dans un précédent article je vous expliquais comment réaliser une pagination avec sql server 2005 en utilisant des techniques comme les cte ou les tables dérivées et en comparant les performances : http://www.thierrybehin.fr/2010/11/17/cte-vs-table-derivee-sql-server/.
Vous pouvez tout oublier, Sql server 2011 utilise une approche beaucoup plus intuitive et performante, il était temps !!!

la syntaxe est trop simple :

DECLARE @RowsPerPage INT = 10, @PageNumber INT = 5

select * from MATable
order by MaColonne
OFFSET @PageNumber*@RowsPerPage ROWS
FETCH NEXT @RowsPerPage ROWS ONLY
GO

Question performance, l’offset est surtout efficace quand on lit les premiers enregistrements d’une table.
Quand on lit les derniers le nombre de lecture logique augmente, le MVP Pinal Dave explique ce phénomène mieux que moi :
http://blog.sqlauthority.com/2010/12/16/sql-server-server-side-paging-in-sql-server-2011-performance-comparison/.

Rendre la propriété maxlength active d’une textbox en mode multiline

Testez en Téléchargeant le projet : TextboxMultilineWithMaxLength (43)

Bizarrement la propriété maxlength est inactive si le mode de la textbox est multiline.
Pour y remedier nous allons donc créer un composant, ce composant devra être facile à intégrer dans un projet et techniquement sans faille, pour cela nous allons intégrer ce composant appelé webcontrol à une bibliotheque de classe UI.

Créez une bilbiothéque de classe et ajoutez un fichier c#, appelez le TextBoxWithMaxLength cette classe hérite de la classe TextBox :

    [ToolboxData(@"<{0}:TextBoxWithMaxLength runat=""server"" \>")]
    public class TextBoxWithMaxLength : TextBox

L’instruction toolboxdata permet de spécifier le tag par défaut quand on fait un drag/drop à partir de la toolbox.

Ce webcontrol utilisera un javascript pour limiter le nombre de caractères tappé, j’utilise le formFieldLimiter : http://www.dynamicdrive.com/dynamicindex16/limitinput.htm
j’ai ajouté quelques modifications à ce javascript pour utiliser le ctrl+a, ctrl+c quand on arrive à la fin de la textbox, testé sur ie6/7/8/Firefox.

Ligne 9 : uncheckedkeycodes: /(8)|(37)|(38)|(39)|(40)|(46)/,
Ligne 15 : var keyunicode=(e.keyCode ? e.keyCode : e.which);
Ligne 17 : if (!this.uncheckedkeycodes.test(keyunicode) && !e.ctrlKey)

ajoutez les fonctions javascript dans un fichier js : formFieldLimiter.js, ajoutez ce fichier à la bilbitohèque de classe, dans les propriétés du fichier : Build Action => Embedded Resource.

appel de la fonction dans TextBoxWithMaxLength

[assembly: WebResource("Test.UI.formFieldLimiter.js", "text/javascript")]
namespace Test.UI
{
    [ToolboxData(@"<{0}:TextBoxWithMaxLength runat=""server"" \>")]
    public class TextBoxWithMaxLength : TextBox
    {
        #region CONSTANTES
            public const string JsRessource = "Test.UI.formFieldLimiter.js";
        #endregion 

   #region EVENTS
        /// <summary>
        /// Pre render
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            if (MaxLength > 0)
            {
                ClientScriptManager cs = this.Page.ClientScript;
                cs.RegisterClientScriptResource(typeof(Test.UI.TextBoxWithMaxLength), JsRessource);
            }
        }

        /// <summary>
        /// Render
        /// </summary>
        /// <param name="e"></param>
        protected override void Render(HtmlTextWriter writer)
        {
            base.Render(writer);
            if (MaxLength > 0)
            {
                string appelJs = @"
                <script type='text/javascript' language='javascript' style='display:none;' >
                fieldlimiter.setup({
                thefield: document.getElementById('" + this.ClientID + @"'),
                maxlength: " + this.MaxLength.ToString() + @",
                statusids: []});
                </script>";

                new Literal() { Text = appelJs }.RenderControl(writer);
            }
        }
        #endregion

Dans le PreRender, l’appel au ClientScriptManager permet de s’assurer qu’il n’y aura qu’une déclaration des fonctions sur une page même si plusieurs contrôles sont utilisés dans une même page.

Dans le Render, on appel la fonction setup du formlimiter pour notre textbox.

Mais si le client désactive le javascript notre fonction de vérification ne sert plus à rien, nous allons la renforcer en obligeant la déclaration d’un RegularExpressionValidator

Déclaration de la propriété RegularExpressionValidatorID :

                #region PROPERTIES
        /// <summary>
        /// Id de RegularExpressionValidator lié à la Textbox
        /// </summary>
        [BindableAttribute(true)]
        [IDReferencePropertyAttribute(typeof(RegularExpressionValidator))]
        [DescriptionAttribute("l'id du RegularExpressionValidator")]
        public string RegularExpressionValidatorID
        {
            get
            {
                return (string)this.ViewState["RegularExpressionValidatorID"] ?? String.Empty;
            }

            set
            {
                this.ViewState["RegularExpressionValidatorID"] = value;
            }
        }
        #endregion

Modification de la méthode PreRender pour vérifier que le regularExpressionValidator est bien paramétré et ajout de l’expression de validation ‘^[\s\S]{0,LeMaxLengthDeLaTextBox}$’ :

        /// <summary>
        /// Pre render
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            if (MaxLength > 0)
            {
                if (string.IsNullOrEmpty(RegularExpressionValidatorID) ||
                    !(FindControl(this.RegularExpressionValidatorID) is RegularExpressionValidator) ||
                    ((RegularExpressionValidator)FindControl(this.RegularExpressionValidatorID)).ControlToValidate != this.ID)
                {
                    throw new Exception("Error the RegularExpressionValidator must be filled and the ControlToValidate must be " + this.ClientID);
                }

                ((RegularExpressionValidator)FindControl(this.RegularExpressionValidatorID)).ValidationExpression = @"^[\s\S]{0," + this.MaxLength.ToString() + "}$";

                ClientScriptManager cs = this.Page.ClientScript;
                cs.RegisterClientScriptResource(typeof(Test.UI.TextBoxWithMaxLength), JsRessource);
            }
        }

Et voici comment utiliser ce contrôle :

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TestWeb._Default" %>
<%@ Register TagPrefix="UI" Assembly="Test.UI"  Namespace="Test.UI" %> 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
   	<UI:TextBoxWithMaxLength RegularExpressionValidatorID="RegularExpressionValidator1" TextMode="MultiLine" MaxLength="10" ID="tbMessagePopup" runat="server" />
		<asp:RegularExpressionValidator runat="server" ID="RegularExpressionValidator1" ErrorMessage="Erreur vous ne pouvez dépasser 10 caractères" ControlToValidate="tbMessagePopup"></asp:RegularExpressionValidator>
    </div>
    </form>
</body>
</html>

Testez en Téléchargeant le projet : TextboxMultilineWithMaxLength (43)

A vous maintenant d’enrichir cet UI en créant vos propres WebControls.

nouveau site petit bateau en ligne

Bonjour, aujourd’hui est un grand jour pour l’agence Altima, leur nouveau bébé http://www.petit-bateau.fr est en ligne.

Au menu nouveau design et nouveaux concepts, parmi ceux-ci :
- un site plus fluide, exemple dans le listing produit il n’y a pas de rafraichissement de page sur le filtrage des produits multicriteres (filtrage par couleur/matiere et taille) de même pour la pagination et le tri :

listing produit petit bateau

Pas de rafraichissement de page aussi dans l’intégralité du checkout (process de commande) tout est effectué sans rechargement de page de la sélection des produits jusqu’à la demande de votre CB:

panier petit bateau

- des produits personnalisés, vous pouvez choisir le texte à imprimer sur vos articles :

produits personnalises petit bateau

- votre valise maternité pour ne rien oublier le jour J :

valise maternite petit bateau

- Sans oublier ce qui ne se voit pas : BackOffice/reprise des comptes existants/importation des produits/suivi de l’état de la commande…

Félicitation à toute l’équipe, Altima peut être fier de son nouveau bébé comme je le suis pour avoir donner ma petite contribution au projet.
De quoi donner des idées pour les fêtes de Noël :)

Stocker des données persistantes spécifiques à une liste sharepoint (comme un nom unique)

Une liste sharepoint peut avoir besoin de données de configuration qui soit stockées de manière persistante.

Par exemple, si l’on créé une liste sharepoint (via code) qui doit être unique, comment pouvoir retrouver cette liste sachant qu’une liste du même nom peut être créé ou que le nom de cette liste peut être modifiée?

Pour se faire nous allons utiliser le dossier associé à la liste : RootFolder de type SpRoot. Via le spRoot on peut stocker des données persistantes grâce à la propriété : Properties.

La création de la liste se fait simplement (j’utilise le template par défaut CustomGrid):

Guid ListGuid = spWeb.Lists.Add(title, description, SPListTemplateType.CustomGrid);
SPList maList = spWeb.Lists[ListGuid];

On va associer à la liste un nom unique appelé NomUniqueDeMaListe (Attention on doit changer le AllowUnsafeUpdate pour réaliser la méthode RootFolder.Update()):

bool unSafeUpdate = maList.ParentWeb.AllowUnsafeUpdates;
try
{
   maList.ParentWeb.AllowUnsafeUpdates = true;
   maList.RootFolder.Properties["NomUnique"] = "NomUniqueDeMaListe";
   maList.RootFolder.Update();
}
finally
{
    maList.ParentWeb.AllowUnsafeUpdates = unSafeUpdate;
}

Maintenant voyons comment retrouver notre liste selon son nom unique (nous allons utiliser une méthode d’extension pour étendre l’objet SpWeb avec le mot clef this):

        /// <summary>
        /// Permet de retourner une liste de splist selon son nom unique (stocké dans le RootFolder de la liste) avec la valeur donnée
        /// </summary>
        /// <param name="spwCurr">WebSite demandé</param>
        /// <param name="value">Valeur</param>
        /// <returns></returns>
        public SPList GetMySpList(this SPWeb spwCurr, string value)
        {
            foreach (SPList list in spwCurr.Lists)
            {
                if (list.RootFolder.Properties.Contains("NomUnique") && list.RootFolder.Properties["NomUnique"].Equals(value))
                {
                    return list;
                }
            }

            return null;
        }

Vous pouvez stocker bien d’autres valeurs de configuration dans le Rootfolder, ce qui vous permettra de catégoriser/configurer vos listes.

les cte, intérêt des requêtes recursives en sql server

Les CTE, Common Table Expression ou Expression de Table Commune sont des requêtes recursives apparuent avec SQL Server 2005 qui permettent dans de nombreux cas de simplifier et d’optimiser le code.

Les CTE ont 2 modes :
- le mode itératif : elles sont alors comparables aux tables dérivées par contre elles peuvent être réutilisées au sein d’une même requête.
- comparaison entre le mode itératif et les tables dérivées : nous verrons les différences de performance et les + qu’apportent les CTE dans la programmation.
- le mode récursif : ce mode nous permet avec simplicité d’utiliser récursivement le résultat d’une requete.

 


 

1 : Les cte en mode itératif :

 

les requêtes cte sont initiés par le mot clef « with » suivi de l’alias donné au résultat de la requete.
exemple :
with monresultat(macolonne)
(select macolonne from matable)
select macolonne from monresultat.

Le helloWorld en cte :

with MonCTE(x)
as
(select 'helloWorld' as x)
select x from MonCTE;

ici la requête cte est: select ‘helloWorld’ as x, elle est utilisée dans l’instruction suivante : select x from MonCTE.

L’exemple le plus probant est l’extraction de résultat parmi des millions/milliards d’enregistrement en vue d’une pagination (comme un extranet listant les commandes d’un site e-commerce).

Pour l’exemple nous allons créer une table commande :

Create table Commande
(
[idCommande] [int] IDENTITY(1,1) NOT NULL,
[nomUser] varchar(255) NOT NULL,
[prenomUser] varchar(255) NOT NULL,
[email] varchar(255) NOT NULL,
[prix] float NOT NULL,
[dateCreation] datetime NOT NULL default GETDATE(),
CONSTRAINT [PK_Commande] PRIMARY KEY CLUSTERED
(
	[IdCommande] ASC
));

insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
values('titi1','toto1','titi1@titi.com',20);
insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
values('titi2','toto2','titi2@titi.com',30);
insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
values('titi3','toto3','titi3@titi.com',4);
insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
values('titi4','toto4','titi4@titi.com',50);
insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
values('titi5','toto5','titi5@titi.com',60);
insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
values('titi6','toto6','titi6@titi.com',70);
insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
values('titi7','toto7','titi7@titi.com',10.55);
insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
values('titi8','toto8','titi8@titi.com',20);
insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
values('titi9','toto9','titi9@titi.com',50);
insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
values('titi10','toto10','titi10@titi.com',2);

Test de montée en charge oblige, nous multiplions le nombre de commande par un million pour cela nous allons utiliser une requête cte recursive (temps estimé 2 à 3 minutes pour un core 2 duo):

with cteCommande  as
(
	select [nomUser],[prenomUser],[email] ,[prix],1 as countMax from commande
		UNION ALL
		select [nomUser],[prenomUser],[email] ,[prix], countMax+1 from cteCommande
		where countMax +1 <=10
),
cteCommande2 as
(
	select [nomUser],[prenomUser],[email] ,[prix],1 as countMax from cteCommande
		UNION ALL
		select [nomUser],[prenomUser],[email] ,[prix], countMax+1 from cteCommande2
		where countMax +1 <=10000
)

insert into Commande ([nomUser],[prenomUser],[email] ,[prix])
select [nomUser],[prenomUser],[email] ,[prix]
from cteCommande2
OPTION	(MAXRECURSION 10001);

Dans cet exemple nous sommes obligés de combiner 2 cte, le nombre de récursion maximale étant de 32765.
La première requête selectionne les 10 enregistrements de la table et * par 10, la deuxieme requête prend les 100 résultats de la premiere requête et multiplie par 10000.
L’option (maxrecursion) permet de s’assurer que nous n’allons pas rentrer dans une boucle sans fin.

 


 

2 : Différence entre les cte itératives et les tables dérivées.

 

Pour choisir la requête classique de pagination, nous allons la générer via linq to sql (on aurait put aussi utiliser linqpad pour générer la requête), pour ça création d’un petit projet application console en c#, ajoutez un dbml au projet (classe Linq to sql) appelez le monDbml, ajoutez la table commande, et ajoutez le code suivant :

 class Program
    {
        static void Main(string[] args)
        {
            StringWriter stw = new StringWriter();

            using (monDbmlDataContext context= new monDbmlDataContext())
            {

                context.Log = stw;
                var l = context.Commande.Skip(500000).Take(10).ToList()
            }
            Console.Write(stw.ToString());
            Console.ReadLine();
        }
    }

en plaçant un espion sur le stw.ToString() à la fin du programme nous pouvons voir la requête générée par linq.
cette requête est :

SELECT [t1].[IdCommande], [t1].[nomUser], [t1].[prenomUser], [t1].[email], [t1].[prix], [t1].[DateCreation]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].[IdCommande], [t0].[nomUser], [t0].[prenomUser], [t0].[email], [t0].[prix], [t0].[DateCreation]) AS [ROW_NUMBER], [t0].[IdCommande], [t0].[nomUser], [t0].[prenomUser], [t0].[email], [t0].[prix], [t0].[DateCreation]
FROM [dbo].[Commande] AS [t0]
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER];

Linq utilise nativement la table dérivée, comparons le temps de traitement à une requête Cte, en Cte la requête de pagination est :


-- notre requête cte
 With cteCommande As(
Select *,ROW_NUMBER() OVER (ORDER BY IdCommande ASC) as
RowNumber
From Commande
)
SELECT cteCommande.IdCommande,cteCommande.NomUser,cteCommande.PrenomUser,cteCommande.Email, cteCommande.[prix], cteCommande.[DateCreation]
FROM cteCommande
WHERE RowNumber BETWEEN @p0 + 1 AND @p0 + @p1;

Pour tester les 2 requêtes nous activons les statistiques et ajoutons les paramétres avant la définition des requêtes :

SET STATISTICS IO ON
SET STATISTICS TIME ON
GO
Declare @p0 as int
Declare @p1 as int
Set @p0 = 500000;
Set @p1 = 10;

-- requête utilisé par Linq
SELECT [t1].[IdCommande], [t1].[nomUser], [t1].[prenomUser], [t1].[email], [t1].[prix], [t1].[DateCreation]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].[IdCommande], [t0].[nomUser], [t0].[prenomUser], [t0].[email], [t0].[prix], [t0].[DateCreation]) AS [ROW_NUMBER], [t0].[IdCommande], [t0].[nomUser], [t0].[prenomUser], [t0].[email], [t0].[prix], [t0].[DateCreation]
FROM [dbo].[Commande] AS [t0]
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER];

-- notre requête cte
 With cteCommande As(
Select *,ROW_NUMBER() OVER (ORDER BY IdCommande ASC) as
RowNumber
From Commande
)
SELECT cteCommande.IdCommande,cteCommande.NomUser,cteCommande.PrenomUser,cteCommande.Email, cteCommande.[prix], cteCommande.[DateCreation]
FROM cteCommande
WHERE RowNumber BETWEEN @p0 + 1 AND @p0 + @p1;

le résultat me donne le même nombre de lecteur logique et sensiblement le même temps d’execution (0.400ms).

autre cas de figure, pour réaliser une pagination il faut aussi connaitre le nombre d’élément répondant aux critères de la requête et ainsi en déduire le nombre de pages. Pourquoi ne pas exploiter le résultat de la première cte pour connaitre le nombre d’éléments dans une autre cte, le prix minimal, maximal pour les user ayant un nom commençant par titi

 With cteCommande As(
Select *,ROW_NUMBER() OVER (ORDER BY IdCommande ASC) as
RowNumber
From Commande
where nomUser like 'titi%'
),
cteCountRow as
(
	select count(1) as nbRow ,max(prix) as maxPrix,min(prix) as minPrix from cteCommande
)
SELECT cteCommande.IdCommande,cteCommande.NomUser,cteCommande.PrenomUser,cteCommande.Email, cteCommande.[prix], cteCommande.[DateCreation],
cteCountRow.nbRow,cteCountRow.maxPrix,cteCountRow.minPrix
FROM cteCommande,cteCountRow
WHERE RowNumber BETWEEN @p0 + 1 AND @p0 + @p1;

Par contre dans la requête classique il nous faut répéter la requête d’extraction des données ce qui donne ce code:

SELECT [t1].[IdCommande], [t1].[nomUser], [t1].[prenomUser], [t1].[email], [t1].[prix], [t1].[DateCreation],[t2].[nbRow],[t2].[maxPrix],[t2].[minPrix]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].[IdCommande], [t0].[nomUser], [t0].[prenomUser], [t0].[email], [t0].[prix], [t0].[DateCreation]) AS [ROW_NUMBER], [t0].[IdCommande], [t0].[nomUser], [t0].[prenomUser], [t0].[email], [t0].[prix], [t0].[DateCreation]
FROM [dbo].[Commande] AS [t0]
where nomUser like 'titi%'
) AS [t1],
(
select count(1) nbRow,max(prix) maxPrix,min(prix) minPrix  from [dbo].[Commande]
where nomUser like 'titi%'
) as [t2]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER];

execution de la requête, et même constat les performances sont identiques le seul intérêt du cte itératif (et pas des moindre) est de pouvoir exploiter le résultat d’une requête sans avoir à la réécrire.
Voici l’article du MVP Pinal Dave qui explique la même chose : http://blog.sqlauthority.com/2007/06/11/sql-server-2005-t-sql-paging-query-technique-comparison-over-and-row_number-cte-vs-derived-table/

Attention par contre à ce cas de figure qui lui execute 3 fois la requête cte et augmente le temps d’execution :


With cteCommande As(
Select *,ROW_NUMBER() OVER (ORDER BY IdCommande ASC) as
RowNumber
From Commande
where nomUser like 'titi%'
)
SELECT cteCommande.IdCommande,cteCommande.NomUser,cteCommande.PrenomUser,cteCommande.Email, cteCommande.[prix], cteCommande.[DateCreation],
nbRow = (select count(1) from cteCommande),maxPrix = (select max(prix) from cteCommande),minPrix= (select min(prix) from cteCommande)
FROM cteCommande
WHERE RowNumber BETWEEN @p0 + 1 AND @p0 + @p1

le plan d’execution nous revele 4 scan d’index sur la table commande au lieu de 2 ce qui alourdit considérablement le temps de réponse.

 


 

3 : les cte récursives

 

Elles se caractérisent par une requête d’initialisation, le mot clef UNION ALL et la requête de récursion, attention vous pouvez entrer dans une boucle infinie, utilisez le mot clef OPTION (MAXRECURSION nombreDeBoucle) pour les éviter.
Pour l’exemple nous allons rechercher les procédures stockées et les vues contenant un texte spécifique.
Si la définition d’une table est modifiée, il est important de vérifier que toutes les procédures stockées ainisi que les vues qui l’utilisent fonctionnent correctement.

Pour les retrouver voici la requete d’initialisation :

SELECT *
FROM sysobjects syso
INNER JOIN syscomments sysc
ON syso.id = sysc.id
WHERE
(syso.xtype = 'P' or
syso.xtype = 'V')
AND
(syso.category = 0)
and text like '%Commande%'

Cette requête n’est pas suffisante, car d’autres procédures stockées ou d’autres vues peuvent utiliser la vue qui contient la table, nous allons donc utiliser la récursion pour les connaitre :

WITH CTE_RechercheRequete(objectName,objectText,objectType,objectReferenceName, objectLevel)
AS
(
		SELECT name,text,syso.xtype , CAST('' AS VARCHAR(MAX)) AS name, 1
		FROM sysobjects syso
		INNER JOIN syscomments sysc
		ON syso.id = sysc.id
		WHERE
		syso.xtype in ('P','V')
		AND
		syso.category = 0
		and text like '%Commande%'
	UNION ALL
		SELECT name,text,syso.xtype,  CAST(c.objectName AS VARCHAR(MAX)), objectLevel +1
		FROM sysobjects syso
		INNER JOIN syscomments sysc
		ON syso.id = sysc.id
		inner join CTE_RechercheRequete c on (sysc.text like '%'+c.objectName+'%' and syso.name <>c.objectName)
		WHERE
		syso.xtype in ('P','V')
		AND
		(syso.category = 0)
	)
select * from
CTE_RechercheRequete
OPTION	(MAXRECURSION 30000);

Cette requête (qui peut être encore optimisée) nous permet d’avoir un bon aperçu de la puissance de la recursion.
A vous de jouer.