Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Atteint de JavaScriptite Aiguë [Cyril Durand]

Expert ASP.net Ajax et WCF, Cyril Durand parle dans son blog de point techniques sur ASP.net, ASP.net Ajax, JavaScript, WCF et .net en général. Cyril est également consultant indépendant, n'hésitez pas à le contacter pour de l'assistance sur vos projets

Actualités

  • Blog de Cyril DURAND, passionné de JavaScript, Ajax, ASP.net et tout ce qui touche au developpement Web Client-Side.

    N'hésitez pas à me contacter pour vos projets .net : architecture, accompagnement, formation, ...

    View Cyril Durand's profile on LinkedIn
    hit counters


    Expertise Commerce server et BizTalk

Gestion des contenus localisés avec Entity Framework – Optimisation de requêtes

Il est fréquent d’avoir du contenu localisé dans une base de données. J’ai récemment eu besoin d’extraire de telles données avec Entity Framework, j’ai alors cherché une solution permettant d’avoir de bonnes performances.

Il existe plusieurs possibilités de modélisation de contenu localisé. Pour ma part, lorsque je dois stocker un objet localisable, je choisi de créer 2 tables : une table contenant les données non localisées et une table contenant les données localisées.

L’identifiant de langue que j’utilise correspond à la propriété LCID d’une culture .net. Pour rappel, une culture .net est constituée d’une langue et, éventuellement, d’une région. Il est ainsi possible d’avoir une culture française neutre, une culture française pour la France (fr-FR), etc. Vous trouverez ci-dessous un tableau listant les cultures que je vais utiliser pour ce post.

image

Ainsi, si je dois stocker des produits, j’aurais la modélisation suivante :

image_thumb1_thumb

Je souhaite utiliser Entity Framework pour récupérer des produits avec le contenu localisé dans une culture donnée. Si le produit n’est pas localisé dans la culture demandée, je souhaite récupérer le contenu dans la culture parente ou la culture neutre.

Par exemple : soit un produit stocké dans la base avec les données localisées pour les cultures “fr”, “en-US” et “” (invariant). Si je demande ce produit en “fr-FR”, je souhaite obtenir le contenu “fr”. Si le produit est localisé dans aucune langue, je souhaite retourner null.

Comme ce genre de requête sera utilisé très fréquemment, j’ai cherché à optimiser les performances. Je me suis principalement concentré sur le nombre de pages lue. En effet, sur ma machine de développement, je n’ai pas de charge, le disque dur est donc peu sollicité, le temps d’exécution de la requête sera donc excellent. Le nombre de pages lues reste constant en fonction de la charge, c’est ce nombre qu’il faut réduire au maximum pour avoir une application qui a de bonnes performances en cas de forte charge.

Afin de faire mes tests, j’ai chargé la base avec un grand nombre de donnée, cela permettra de bien mettre en avant les problèmes IO. Les tables Product et ProductLocalized contiennent respectivement 1.2 et 6.3 millions de ligne.

Afin de ne pas travailler sur les 1.2 millions de ligne, je filtre ma requête par TypeId. Un TypeId est associé à environ 100 produits. Pour optimiser les requêtes, j’ai ajouté l’index suivant :

CREATE NONCLUSTERED INDEX [IX_Product_TypeId] ON [dbo].[Product] 
(
    [TypeId] ASC
)
INCLUDE ([ProductId], [SKU]) 

J’utilise SQL Server édition développeur et EF 4.0, chacun de mes tests sont joués plusieurs fois, le plan d’exécution est donc en cache. Les résultats affichés sont une moyenne des dernières exécutions. Les données proviennent de SQL Profiler.

La première requête que j’ai écrite a été la suivante :

image_thumb8_thumb

Les performances de cette requête sont les suivantes :

image

J’ai ensuite modifié la requête pour factoriser les différents IF, ma requête est devenue la suivante :

image_thumb11_thumb

Les résultats sont les suivants :

image

Cette requête est moitié moins couteuse que la première. Pour ce cas, Entity Framework ne mutualise pas les bouts d’expression identiques, il est donc plus optimisé de faire une pré-requête qui permettra de préselectionner les données.

La requête SQL et plan d’exécution correspondants sont les suivants :

image

image_thumb25_thumb

Il y a 6 Index Seek dont 5 clustered . Le besoin est seulement de récupérer des données provenant de 2 tables, il doit être possible d’obtenir 2 Index Seek.

J’ai ensuite essayé la requête EF suivante :

image_thumb27_thumb

La requête générée et le plan d’exécution sont les suivants :

image

image_thumb30_thumb

Les performances sont les suivantes :

image

Le plan d’exécution n’a que 2 Index Seek, cependant les performances sont bien moins bonnes que la requête précédente. Le cout principal de la requête est un sort. Ce sort est fait sur un “case when”, il n’est donc pas possible d’ajouter un index pour optimiser la requête.

Je suis donc parti sur une toute autre piste en utilisant une jointure entre les cultures souhaitées et les enregistrements demandés. J’ai alors obtenu la requête suivante :

image_thumb17_thumb

La syntaxe est un peu étrange, on fait une jointure entre un tableau d’objet anonyme en mémoire et la table ProductLocalized.

La requête SQL et le plan d'exécution correspondants sont les suivants :

image

image_thumb22_thumb

Les résultats :

image

Le plan d’exécution parait complexe, cependant il n’y a 2 Index Seek, toutes les autres opérations sont des petites opérations sur des constantes ou autres. Les performances sont 10 fois meilleures que la première requête. J’ai essayé de faire la jointure dans l’autre sens, il n’y a pas eu de différence.

On voit donc qu’il est important de tester et analyser les différentes requêtes générée par Entity Framework. Il est souvent possible d’obtenir un même résultat en écrivant la requête de façon différente, l’analyse de la requête et de son plan d’exécution permet parfois des grands gains de performance. Par rapport à ma requête initiale, je suis passé de de 5000 pages lues à 480, soit un gain supérieur à 1000%.

Pour ma part, je ne pense pas qu’il soit possible d’optimiser davantage cette requête. Avez-vous une autre idée pour optimiser encore plus cette requête ? Que ce soit en SQL ou avec Entity Framework ?

Posted: lundi 31 octobre 2011 23:51 par cyril
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

Poppyto a dit :

L'optimisation avec EF est intéressante, mais on a forcément le niveau intermédiaire (LinqToSql) qui apporte sont lot de limites...l'utilisation de procedures stockées changerait la donne (+ de manip possibles)

La requête me semble donc optimisable, si tu files un backup de la base je veux bien jeter un coup d'oeil (là comme ça sans maniper c'est pas simple et je n'arrive pas à voir le volume de données sorties !)

# novembre 1, 2011 15:50

cyril a dit :

Poppyto, la base fait 2Go :)

Voici le script de création de la base :

USE [Test]

GO

/****** Object:  Table [dbo].[Product]    Script Date: 11/01/2011 18:20:00 ******/

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE TABLE [dbo].[Product](

[ProductId] [uniqueidentifier] NOT NULL,

[SKU] [nvarchar](50) NOT NULL,

[TypeId] [int] NOT NULL,

CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED

(

[ProductId] 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

/****** Object:  Table [dbo].[Culture]    Script Date: 11/01/2011 18:20:00 ******/

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

SET ANSI_PADDING OFF

GO

CREATE TABLE [dbo].[Culture](

[CultureId] [int] NOT NULL,

[ShortName] [varchar](11) NOT NULL,

CONSTRAINT [PK_Culture] PRIMARY KEY CLUSTERED

(

[CultureId] 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

SET ANSI_PADDING OFF

GO

/****** Object:  Table [dbo].[ProductLocalized]    Script Date: 11/01/2011 18:20:00 ******/

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE TABLE [dbo].[ProductLocalized](

[ProductId] [uniqueidentifier] NOT NULL,

[CultureId] [int] NOT NULL,

[Name] [nvarchar](50) NOT NULL,

[Description] [nvarchar](max) NULL,

CONSTRAINT [PK_ProductLocalized] PRIMARY KEY CLUSTERED

(

[ProductId] ASC,

[CultureId] 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

/****** Object:  Default [DF_Product_TypeId]    Script Date: 11/01/2011 18:20:00 ******/

ALTER TABLE [dbo].[Product] ADD  CONSTRAINT [DF_Product_TypeId]  DEFAULT (rand()*(10000)) FOR [TypeId]

GO

/****** Object:  ForeignKey [FK_ProductLocalized_Culture]    Script Date: 11/01/2011 18:20:00 ******/

ALTER TABLE [dbo].[ProductLocalized]  WITH CHECK ADD  CONSTRAINT [FK_ProductLocalized_Culture] FOREIGN KEY([CultureId])

REFERENCES [dbo].[Culture] ([CultureId])

GO

ALTER TABLE [dbo].[ProductLocalized] CHECK CONSTRAINT [FK_ProductLocalized_Culture]

GO

/****** Object:  ForeignKey [FK_ProductLocalized_Product]    Script Date: 11/01/2011 18:20:00 ******/

ALTER TABLE [dbo].[ProductLocalized]  WITH CHECK ADD  CONSTRAINT [FK_ProductLocalized_Product] FOREIGN KEY([ProductId])

REFERENCES [dbo].[Product] ([ProductId])

GO

ALTER TABLE [dbo].[ProductLocalized] CHECK CONSTRAINT [FK_ProductLocalized_Product]

GO

et le bout de code pour peupler la base

       static void PopulateData()

       {

           using (SqlConnection conn = new SqlConnection("data source=.;initial catalog=Test;integrated security=True;multipleactiveresultsets=True;"))

           using (SqlCommand productCommand = new SqlCommand(@"

INSERT INTO [Test].[dbo].[Product]

          ([ProductId]

          ,[SKU])

    VALUES

          (@ProductId

          ,@SKU)

", conn))

           using (SqlCommand productLocalizedCommand = new SqlCommand(@"

INSERT INTO [Test].[dbo].[ProductLocalized]

          ([ProductId]

          ,[CultureId]

          ,[Name]

          ,[Description])

    VALUES

          (@ProductId

          ,@CultureId

          ,@Name

          ,@Description)

", conn))

           {

               productCommand.Parameters.Add("ProductId", SqlDbType.UniqueIdentifier);

               productCommand.Parameters.Add("SKU", SqlDbType.NVarChar);

               productLocalizedCommand.Parameters.Add("ProductId", SqlDbType.UniqueIdentifier);

               productLocalizedCommand.Parameters.Add("CultureId", SqlDbType.Int);

               productLocalizedCommand.Parameters.Add("Name", SqlDbType.NVarChar);

               productLocalizedCommand.Parameters.Add("Description", SqlDbType.NVarChar);

               conn.Open();

               Random rnd = new Random();

               int[] cultureIds = new int[] { 12, 9, 127, 1033, 1036 };

               for (int i = 0; i < 100000; i++)

               {

                   Guid productId = Guid.NewGuid();

                   productCommand.Parameters["ProductId"].Value = productId;

                   productCommand.Parameters["SKU"].Value = "SKU_" + Path.GetRandomFileName() + "_" + i.ToString();

                   productCommand.ExecuteNonQuery();

                   for (int j = 0; j < cultureIds.Length; j++)

                   {

                       productLocalizedCommand.Parameters["ProductId"].Value = productId;

                       productLocalizedCommand.Parameters["CultureId"].Value = cultureIds[j];

                       productLocalizedCommand.Parameters["Name"].Value = "Name " + Path.GetRandomFileName() + cultureIds[j].ToString();

                       productLocalizedCommand.Parameters["Description"].Value = "Description " + Path.GetRandomFileName() + cultureIds[j].ToString();

                       productLocalizedCommand.ExecuteNonQuery();

                   }

                   Console.WriteLine(i + " " + Thread.CurrentThread.ManagedThreadId);

               }

               conn.Close();

           }

       }

Bon courage :)

# novembre 1, 2011 18:21

Poppyto a dit :

La première requête n'est pas top il y a deux jointures en trop (sic!), ça devrait être :

SELECT

[Extent1].[TypeId] AS [TypeId],

[Extent1].[ProductId] AS [ProductId],

[Extent1].[SKU] AS [SKU],

CASE WHEN (([Extent2].[ProductId] IS NULL) AND ([Extent2].[CultureId] IS NULL))  

THEN

CASE WHEN (([Extent3].[ProductId] IS NULL) AND ([Extent3].[CultureId] IS NULL))

THEN

[Extent4].[Name]

ELSE  [Extent3].[Name]

END

ELSE [Extent2].[Name]

END

AS [C1],

CASE WHEN (([Extent2].[ProductId] IS NULL) AND ([Extent2].[CultureId] IS NULL))  

THEN

CASE WHEN (([Extent3].[ProductId] IS NULL) AND ([Extent3].[CultureId] IS NULL))

THEN

[Extent4].[Description]

ELSE  [Extent3].[Description]

END

ELSE [Extent2].[Description]

END

AS [C2]

FROM

[dbo].[Product] AS [Extent1]

LEFT OUTER JOIN [dbo].[ProductLocalized] AS [Extent2] ON ([Extent1].[ProductId] =  [Extent2].[ProductId]) AND (1036= [Extent2].[CultureId])

LEFT OUTER JOIN [dbo].[ProductLocalized] AS [Extent3] ON ([Extent1].[ProductId] =  [Extent3].[ProductId]) AND (9= [Extent3].[CultureId])

LEFT OUTER JOIN [dbo].[ProductLocalized] AS [Extent4] ON ([Extent1].[ProductId] =  [Extent4].[ProductId]) AND (127= [Extent4].[CultureId])

WHERE

124=[Extent1].[TypeId]

---------------------------------

PS : dommage que tu n'ai pas filé les requêtes au format texte, ça m'aurait aidé :D.

Sinon j'ai tenté aussi avec une CTE mais ça n'a pas abouti niveau perf :

----------------------------------

-- Define the CTE expression name and column list.

WITH Lang_CTE (TypeID, ProductId, SKU,Name,Description,id)

AS

-- Define the CTE query.

(

SELECT

[Extent1].[TypeId] AS [TypeId],

[Extent1].[ProductId] AS [ProductId],

[Extent1].[SKU] AS [SKU],

[Extent2].[Name] as [C1],

[Extent2].[Description] as [C2],

t1.id

FROM

[dbo].[Product] AS [Extent1]

INNER JOIN (SELECT 1 as id,1036 as lcid

UNION SELECT 2,9

UNION SELECT 3,127) as t1 ON 1=1

INNER JOIN [dbo].[ProductLocalized] AS [Extent2]

ON ([Extent1].[ProductId] =  [Extent2].[ProductId] AND CultureId=t1.lcid)

WHERE

3546=[Extent1].[TypeId]

)

-- Define the outer query referencing the CTE name.

SELECT t1.* FROM Lang_CTE as t1

INNER JOIN

(

SELECT [ProductId] AS [ProductId],MIN(id) as id

FROM Lang_CTE

GROUP BY [ProductId]

) r3

ON (r3.id=t1.id AND r3.ProductId=t1.ProductId)

------------------------------------------------

(PS:Je n'ai pas de solution EF puisque je ne l'utilise pas (sauf sur sharepoint))

# novembre 2, 2011 13:35

Matthieu MEZIL a dit :

C'est moi où il manque le code pour créer les cultures en base ?

# novembre 3, 2011 22:19

cyril a dit :

@Matthieu :

oui, il suffit de dumper les cultures de .net.

Voici un bout de SQL pour créer les cultures pour jouer :

CREATE TABLE [dbo].[Culture](

[CultureId] [int] NOT NULL,

[ShortName] [varchar](11) NOT NULL,

CONSTRAINT [PK_Culture] PRIMARY KEY CLUSTERED

(

[CultureId] 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

SET ANSI_PADDING OFF

GO

INSERT [dbo].[Culture] ([CultureId], [ShortName]) VALUES (9, N'en')

INSERT [dbo].[Culture] ([CultureId], [ShortName]) VALUES (12, N'fr')

INSERT [dbo].[Culture] ([CultureId], [ShortName]) VALUES (127, N'')

INSERT [dbo].[Culture] ([CultureId], [ShortName]) VALUES (1033, N'en-US')

INSERT [dbo].[Culture] ([CultureId], [ShortName]) VALUES (1036, N'fr-FR')

INSERT [dbo].[Culture] ([CultureId], [ShortName]) VALUES (2060, N'fr-BE')

(J'ai pas le code sous la main qui permet de dumper .net)

>> Bon jeu :)

# novembre 3, 2011 23:28

cyril a dit :

Poppyto : désolé pour les requêtes en Image, le copier/coller de code SQL est bizarrement bloqué par le système de blog :(

Si tu as besoin d'autres bout de SQL, hésite pas à demander, je peux les mettre en commentaire :-)

Pour la CTE, comme tu l'as dit, c'est moins bon niveau perf, il y a 1500 pages lues :)

Quand je regarde le plan d'execution, je ne vois pas comment il peut être possible d'optimiser davantage cette requête, mais je reste curieux.

# novembre 3, 2011 23:45

Matthieu MEZIL a dit :

Salut Cyril

Effectivement, comme je le martelle depuis très très très longtemps, les performances des requêtes (pour le même résultat) peut être très différents.

Avec l'abstraction apportée par L2E, il est parfois difficile de prédire quel sera la meilleure requête. Personnellement, malgré une très grosse expérience / expertise sur EF, il m'arrive d'avoir des doutes sur la meilleure façon d'écrire une même requête.

Malheureusement, j'ai souvent un peu l'impression d'être le seul à en parler.

Aussi, c'est avec un grand plaisir que j'ai lu ton post.

Pour ta requête, tu devrais tenter celle-ci :

int[] culturesIds = { 1036, 9, 127 };

var q2 = (from p in entities.Products

         where p.TypeId == 124

         let productLocalized = (from pl in p.ProductLocalizeds

                                 where culturesIds.Contains(pl.CultureId)

                                 let index = pl.CultureId == 1036 ? 0 : pl.CultureId == 9 ? 1 : 2

                                 orderby index

                                 select pl).FirstOrDefault()

         select new

         {

             p.ProductId,

             p.SKU,

             productLocalized.Name,

             productLocalized.Description

         }

             )

       .ToList();

Dans mes tests, j'observe une légère dégradation des Reads de l'ordre de 2 % mais une amélioration de la Duration de l'ordre de 10 %.

# novembre 4, 2011 14:54

cyril a dit :

Matthieu,

D'après mes tests, la requête que tu me proposes prend 1433 pages lues contre 482 pour la solution avec le join.

Le principal cout est le OrderBy, nécessaire pour récuperer la bonne culture, sans ce orderby, la requête est de 607 pages lues.

Je reste donc sur la solution du Join

:-)

# novembre 5, 2011 20:32

Matthieu MEZIL a dit :

Peux-tu partager ta vraie base stp ?

J'ai utilisé ton code pour remplir la base et j'y ai mis l'ensemble des cultures .NET et je n'ai pas du tout le même résultat comme évoqué précédemment...

# novembre 6, 2011 17:34

Matthieu MEZIL a dit :

Autre point ?

Qu'utilises-tu pour connaître le nombre de pages lues ? (j'utilise le profiler)

# novembre 6, 2011 17:39

cyril a dit :

Ma base fait 300Mo après compression, je n'ai pas une connexion suffisante aujourd'hui pour l'uploader quelque part.

Voici le détail de mes tables :

- Culture : 301 lignes

- Product : 1 252 874 lignes

- ProductLocalized : 6 264 287

J'ai un index sur la table Product (ProductType).

J'utilise SQL 2008 R2 developper et SQL profiler pour regarder ce qui transite.

Mes index n'ont pas de fragmentation.

Le code que j'utilise est le suivant :

       static void TestLocalization()

       {

           for (int k = 0; k < 5; k++)

           {

               TestEntities entities = new TestEntities();

               var q = entities.Products

                               .Where(p => p.TypeId == 124)

                               .Select(o => new

                               {

                                   Product = o,

                                   ProductLocalized = o.ProductLocalizeds.FirstOrDefault(pl => pl.CultureId == 1036) ??

                                                           o.ProductLocalizeds.FirstOrDefault(pl => pl.CultureId == 9) ??

                                                               o.ProductLocalizeds.FirstOrDefault(pl => pl.CultureId == 127)

                               })

                               .Select(o => new

                               {

                                   o.Product.ProductId,

                                   o.Product.SKU,

                                   Name = o.ProductLocalized.Name,

                                   Description = o.ProductLocalized.Description

                               });

               q.ToList();

               q = entities.Products

                          .Where(p => p.TypeId == 124)

                          .Select(p => new

                          {

                              Product = p,

                              ProductLocalized =

                                  new[] { new { Index = 0, CultureId = 1036 }, new { Index = 0, CultureId = 9 }, new { Index = 0, CultureId = 127 } }

                                  .Join(p.ProductLocalizeds, o => o.CultureId, i => i.CultureId, (o, i) => new { Position = o.Index, ProductLocalized = i })

                                  .OrderBy(o => o.Position)

                                  .Select(o => o.ProductLocalized)

                                  .FirstOrDefault()

                          })

                          .Select(o => new

                          {

                              o.Product.ProductId,

                              o.Product.SKU,

                              Name = o.ProductLocalized.Name,

                              Description = o.ProductLocalized.Description

                          });

               q.ToList();

               q = entities.Products

                          .Where(p => p.TypeId == 124)

                          .Select(o => new

                          {

                              Product = o,

                              ProductLocalized = o.ProductLocalizeds

                                                  .Select(pl => new { ProductLocalized = pl, Position = pl.CultureId == 1036 ? 1 : pl.CultureId == 9 ? 2 : 3 })

                                                  .OrderBy(p => p.Position)

                                                  .Select(p => p.ProductLocalized)

                                                  .FirstOrDefault()

                          })

                          .Select(o => new

                          {

                              o.Product.ProductId,

                              o.Product.SKU,

                              Name = o.ProductLocalized.Name,

                              Description = o.ProductLocalized.Description

                          });

               q.ToList();

               q = entities.Products

                               .Where(p => p.TypeId == 124)

                               .Select(p => new

                               {

                                   Product = p,

                                   ProductLocalized = p.ProductLocalizeds

                                                       .Join(new[] { new { Index = 0, CultureId = 1036 }, new { Index = 0, CultureId = 9 }, new { Index = 0, CultureId = 127 } }, o => o.CultureId, i => i.CultureId, (o, i) => new { Position = i.Index, ProductLocalized = o })

                                                       .OrderBy(o => o.Position)

                                                       .Select(o => o.ProductLocalized)

                                                       .FirstOrDefault()

                               })

                               .Select(o => new

                               {

                                   o.Product.ProductId,

                                   o.Product.SKU,

                                   Name = o.ProductLocalized.Name,

                                   Description = o.ProductLocalized.Description

                               });

               q.ToList();

               int[] culturesIds = { 1036, 9, 127 };

               var q2 = (from p in entities.Products

                         where p.TypeId == 124

                         let productLocalized = (from pl in p.ProductLocalizeds

                                                 where culturesIds.Contains(pl.CultureId)

                                                 let index = pl.CultureId == 1036 ? 0 : pl.CultureId == 9 ? 1 : 2

                                                 orderby index

                                                 select pl).FirstOrDefault()

                         select new

                         {

                             p.ProductId,

                             p.SKU,

                             productLocalized.Name,

                             productLocalized.Description

                         }

                       )

                      .ToList();

           }

       }

Les résultats pour la 5ème execution sont les suivants :

 0  2525  0  3

 0   482  0  1

 0   617  0  6

 0   482  0  2    

 0  1433  0  7    

# novembre 6, 2011 19:00

Poppyto a dit :

J'ai un peu réfléchi ( :) ) et à mon avis il y a un souci de conception.

Enfait la valeur invariant (LCID) devrait être une colonne de la table Product et non plus une ligne de ProductLocalizer & Culture

De ce fait il n'y aurait qu'une jointure outer avec la langue sélectionnée, et si elle est null, on renvoie la colonne invariant.

SELECT

[Extent1].[TypeId] AS [TypeId],

[Extent1].[ProductId] AS [ProductId],

[Extent1].[SKU] AS [SKU],

ISNULL(Extent2.Description,Extent1.DescriptionInvariant) as Description

FROM

[dbo].[Product] AS [Extent1]

LEFT OUTER JOIN [dbo].[ProductLocalized] AS [Extent2] ON ([Extent1].[ProductId] =  [Extent2].[ProductId]) AND (1036= [Extent2].[CultureId])

WHERE

124=[Extent1].[TypeId]

Tu devrais avoir les meilleurs performances comme ça.

# novembre 8, 2011 15:51

cyril a dit :

@Poppyto : Si la langue est dans le produit, comment on fait pour avoir des produits multi-lingues ?

# novembre 8, 2011 16:55

Poppyto a dit :

Non pas la langue, la valeur pour la langue neutral (valeur par défaut quoi).

On ne s'est peut-être pas compris, et comme je n'ai pas tout le contexte en tête...

# novembre 23, 2011 10:21
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Compte rendu : SharePoint / O365 : des pratiques pour une meilleure productivité par The Mit's Blog le 12-12-2014, 18:11

- [TFS] Suppression des feature SQL Entreprise en masse par Blog de Jérémy Jeanson le 12-06-2014, 09:18

- [Clean Code] règles de nommage par Fathi Bellahcene le 12-04-2014, 22:59

- Windows To Go 10 et Upgrades impossibles par Blog de Jérémy Jeanson le 12-04-2014, 21:38

- SharePoint OnPremise: Statistiques d’utilisation pour traquer les sites fantomes par Blog Technique de Romelard Fabrice le 12-03-2014, 10:28

- SharePoint 2007: Script PowerShell permettant le backup de toutes les collections de sites d’une application Web par Blog Technique de Romelard Fabrice le 12-02-2014, 10:00

- Xamarin : un choix précieux par .net is good... C# is better ;) le 12-01-2014, 15:10

- Office 365: Comparaison des composants pour préparer votre migration de SharePoint 2007 vers Office 365 par Blog Technique de Romelard Fabrice le 11-28-2014, 16:20

- Créer un périphérique Windows To Go 10 ! par Blog de Jérémy Jeanson le 11-21-2014, 04:54

- RDV à Genève le 12 décembre pour l’évènement “SharePoint–Office 365 : des pratiques pour une meilleure productivité !” par Le blog de Patrick [MVP Office 365] le 11-19-2014, 10:40