none
Linq To Entities - Insertion dans le mauvais ordre RRS feed

  • Question

  • - Xp Pro SP3

    - Visual Studio 2010

    - Framework 4.0

     

    Bonjour, 

    J'ai remarqué que Linq To Entities avait tendance a insérer les données dans l'ordre qui lui plais  (surement pour des questions d'optimisation).

    J'aurai aimé savoir si on pouvait empêcher cette modification d'ordre lors de l'insertion.

     

    Voici un exemple concret :

    Dans une base School nous avons 3 tables :

    - Families

    - Items

    - FamiliesItems

    Voici le script de création des tables : (avec les données à la fin)

     

    BEGIN TRANSACTION
    SET QUOTED_IDENTIFIER ON
    SET ARITHABORT ON
    SET NUMERIC_ROUNDABORT OFF
    SET CONCAT_NULL_YIELDS_NULL ON
    SET ANSI_NULLS ON
    SET ANSI_PADDING ON
    SET ANSI_WARNINGS ON
    COMMIT
    BEGIN TRANSACTION
    GO
    CREATE TABLE dbo.Items
    	(
    	ItemId int NOT NULL IDENTITY (1, 1),
    	Description varchar(50) NOT NULL
    	) ON [PRIMARY]
    GO
    ALTER TABLE dbo.Items ADD CONSTRAINT
    	PK_Items PRIMARY KEY CLUSTERED 
    	(
    	ItemId
    	) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    
    GO
    ALTER TABLE dbo.Items SET (LOCK_ESCALATION = TABLE)
    GO
    COMMIT
    BEGIN TRANSACTION
    GO
    CREATE TABLE dbo.Families
    	(
    	FamilyId int NOT NULL IDENTITY (1, 1),
    	Description varchar(50) NOT NULL
    	) ON [PRIMARY]
    GO
    ALTER TABLE dbo.Families ADD CONSTRAINT
    	PK_Families PRIMARY KEY CLUSTERED 
    	(
    	FamilyId
    	) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    
    GO
    ALTER TABLE dbo.Families SET (LOCK_ESCALATION = TABLE)
    GO
    COMMIT
    BEGIN TRANSACTION
    GO
    CREATE TABLE dbo.FamiliesItems
    	(
    	FamilyId int NOT NULL,
    	ItemId int NOT NULL
    	) ON [PRIMARY]
    GO
    ALTER TABLE dbo.FamiliesItems ADD CONSTRAINT
    	PK_FamiliesItems PRIMARY KEY CLUSTERED 
    	(
    	FamilyId,
    	ItemId
    	) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    
    GO
    ALTER TABLE dbo.FamiliesItems ADD CONSTRAINT
    	FK_FamiliesItems_Families FOREIGN KEY
    	(
    	FamilyId
    	) REFERENCES dbo.Families
    	(
    	FamilyId
    	) ON UPDATE NO ACTION 
    	 ON DELETE CASCADE 
    	
    GO
    ALTER TABLE dbo.FamiliesItems ADD CONSTRAINT
    	FK_FamiliesItems_Items FOREIGN KEY
    	(
    	ItemId
    	) REFERENCES dbo.Items
    	(
    	ItemId
    	) ON UPDATE NO ACTION 
    	 ON DELETE CASCADE 
    	
    GO
    ALTER TABLE dbo.FamiliesItems SET (LOCK_ESCALATION = TABLE)
    GO
    
    INSERT [dbo].[Families] ([FamilyId], [Description]) VALUES (1, N'Red')
    INSERT [dbo].[Families] ([FamilyId], [Description]) VALUES (2, N'Blue')
    INSERT [dbo].[Families] ([FamilyId], [Description]) VALUES (3, N'Yellow')
    
    INSERT [dbo].[Items] ([ItemId], [Description]) VALUES (1, N'Car')
    INSERT [dbo].[Items] ([ItemId], [Description]) VALUES (2, N'Bus')
    
    COMMIT
    

     

    Importé dans Linq to Entities cela nous donne 2 objets : Family et Item reliés par une association * -- * (plusieurs à plusieurs).

     

    Nous avons 3 lignes dans la table Families :

    - Red

    - Blue

    - Yellow

     

    et 2 lignes dans la table Items :

    - Car

    - Bus

     

    Maintenant nous allons ajouter au premier élément de families (Red)  les 2 éléments d'Items dans l'ordre suivant : Bus puis Car:

     

    using (SchoolEntities context = new SchoolEntities())
    {
     context.Families.First().Items.Add(context.Items.First(i => i.ItemId == 2)); //Bus
     context.Families.First().Items.Add(context.Items.First(i => i.ItemId == 1)); //Car
     context.SaveChanges();
    }
    

    En toute logique nous devrions retrouver les lignes suivantes dans FamiliesItems :

    FamilyId    ItemId

        1            2

        1            1

     

    Mais voila ce que l'on retrouve :

     

    FamilyId    ItemId

        1            1

        1            2

     

    Pouvez vous m'indiquer comment forcer Linq to Entities à insérer les données dans l'ordre qu'on lui donne svp ?

     

    Bien-sure je pourrai résoudre le pb en ajoutant à la table FamiliesItems un champ Order numérique qui prendrait l'ordre d'insertion, et après à l'affichage je trirai suivant Order, mais cette solution casse le modèle objet à 2 tables de Linq To Entities (en effet il ajoute la table FamiliesItems dans le schéma).

     

    D'avance merci de vos réponses.


    Il est bon de savoir, mais il est préférable d'apprendre.
    dimanche 19 septembre 2010 15:02

Réponses

  • Bonjour,

    Pour la récupération de ces billes, elle sont (a mon avis) implicitement numérotés par Sql Server si aucune autre indication n'est fourni (Order By). Je pense que cette numérotation correspond a l'identifiant interne unique de la base.
    Si vous ne spécifiez pas de tri (ORDER BY), Sql Server ne peut pas garantir dans quel ordre vous allez récupérer les lignes (l'ordre est complétement aléatoire et dépend des données mise en cache au moment de la requête, des indexes utilisés, ...etc). Il ne font donc pas baser votre application sur ce tri aléatoire, mais en spécifier un tri explictement (ajoutez si nécessaire une colonne).

    Voyez les tables comme un ensemble mathématique...

    En ce qui concerne l'Entity Framework, ce dernier dispose d'un Tracker qui permet de suivre les entités. C'est ce tracker qui maintient une liste d'entité à mettre à jour et cette liste n'est nullement garantie comme ordonnée par Microsoft. On ne peut donc garantir que les entités seront envoyés dans un ordre précis.

    Cordialement


    Gilles TOURREAU - MVP C# - MCTS ADO .NET 3.5 - MCPD Windows Developper 3.5 - Architecte .NET/Consultant/Formateur - http://gilles.tourreau.fr
    • Marqué comme réponse ddv_ lundi 20 septembre 2010 15:22
    lundi 20 septembre 2010 15:17
    Modérateur
  • Bonjour,

    en fait entity framework gére l'ordre de façon assez complexe pour pouvoir respecter des contraintes d'intégrité référentielle par exemple...

    Pour des insert qui n'aurait pas de dépendances il semble qu'entity framework ne gère pas l'ordre.

    Vous trouvez ici un lien sur ce point : row-insertion-order-entity-framework

    Concernant RowId j'imagine plutot que c'est le select qui utilise ce tri par défaut. Vous pourriez peut être obtenir un autre tri par défaut si un index existait sur la table...Néanmoins cela ne semble pas lié à l'ordre aléatoire des insertions.

    Cordialement

     

     

    • Marqué comme réponse ddv_ lundi 20 septembre 2010 15:24
    lundi 20 septembre 2010 15:22

Toutes les réponses

  • Bonjour,

    Si on met de côté Entity Framework, les SGBD SQL ne définit pas de relation d'ordre dans les lignes des tables (la notion d'enregistrement à la COBOL n'existe pas !). Si vous souhaitez obtenir des lignes SQL (ou des entités au niveau Entity Framework) dans un certain ordre, c'est à vous de spécifier l'ordre explicitement (dans la clause ORDER BY du SELECT SQL ou de Linq).

    Voyez une table SQL comme un sac de billes : Lorsque vous prennez 5 billes, vous ne pouvez pas garantir qu'elle seront tirées dans le même ordre. Par contre si vous marquez dessus un numéro, vous pouvez après les avoir tiré, les trier suivant leur numéro.

    Dans votre cas, vous devrez ajouter une colonne permettant de trier vos entités dans l'ordre voulu.

    Cordialement


    Gilles TOURREAU - MVP C# - MCTS ADO .NET 3.5 - MCPD Windows Developper 3.5 - Architecte .NET/Consultant/Formateur - http://gilles.tourreau.fr
    dimanche 19 septembre 2010 17:38
    Modérateur
  • Merci de votre réponse,

    Comme je l'ai signalé à la fin de mon message, je voudrais éviter de casser le schéma de mes entités (2 tables avec liaison *<->*).

    Ce problème ne se reproduit pas sur Linq To Sql car il y a une entité intermédiaire qui est créé : FamilliesItem.

    J'ai poursuivi mes essais et j'ai comparé l'ordre dans lequel Linq To Sql et Linq To Entities envoi les requêtes d'insertion :

    Grâce à Sql Server Profiler j'ai pu constater que l'ordre d'insertion soumis à SQL Server par Linq To Entities est incorect. Dans mon exemple Linq To Entities transmet en premier lieu l'insert de "Car" puis l'insert de "Bus".

     

    Avec le code suivant sous Linq To Sql :

     

    using (SchoolDataContext context = new SchoolDataContext())
       {
        context.FamiliesItem.InsertOnSubmit(new FamiliesItem { Family = context.Family.First(), Item = context.Item.First(i => i.ItemId == 2) });
        context.FamiliesItem.InsertOnSubmit(new FamiliesItem { Family = context.Family.First(), Item = context.Item.First(i => i.ItemId == 1) });
    
        context.SubmitChanges();
       }
    

     

    L'ordre est respecté.

     

    Je comprend tout a fait que Linq To Entities ne puisse pas garantir l'ordre d'insertion, dans ce cas j'aimerais savoir si il existe un moyen de mapper mes entités pour que le numéro d'ordre apparaisse et que j'ai toujours 2 entités (Family et Item) associé en *<->*.

     

    Malgré de nombreuses tentatives, je n'y suis pas parvenu.

     

     

    Je reviens sur votre réponse :

     

    "Voyez une table SQL comme un sac de billes : Lorsque vous prennez 5 billes, vous ne pouvez pas garantir qu'elle seront tirées dans le même ordre. Par contre si vous marquez dessus un numéro, vous pouvez après les avoir tiré, les trier suivant leur numéro."

    Pour la récupération de ces billes, elle sont (a mon avis) implicitement numérotés par Sql Server si aucune autre indication n'est fourni (Order By). Je pense que cette numérotation correspond a l'identifiant interne unique de la base.

     

    Mais je peux me tromper.

     


     

     


    Il est bon de savoir, mais il est préférable d'apprendre.
    dimanche 19 septembre 2010 18:06
  • Bonjour,

    à ma connaissance Entity Framework, tout du moins en utilisant le designer, gére les associations many to many sans faire apparaitre d'entités intermédiaires uniquement si la table d'association ne contient que les clés :

    - Tant que la table FamiliesItems ne contient que les colonnes FamilyId et ItemId vous conserverez votre modèle d'entités actuelle avec la relation * <--> * et la table d'association est cachée.

    - Si vous ajoutez une colonne supplémentaire ( un champ DateTime ou un critère d'ordre par exemple ) alors la table d'association apparaitra dans le modèle à l'image de ce qui existait dans LinqToSql

    Vous pourrez trouver un article ici évoquant ce sujet :

    http://learnentityframework.com/LearnEntityFramework/tutorials/many-to-many-relationships-in-the-entity-data-model/

    Cordialement

    lundi 20 septembre 2010 14:43
  • Merci de votre réponse,

    C'est bien ce que je redoutai.

    Ceci n'explique pas pourquoi Linq To Entities envoi les requêtes d'insert dans le mauvais ordre...

    Toujours est-il que je vais devoir modifier mes entités.

     

    Je laisse la question ouverte au cas où quelqu'un pourrai expliquer les raisons du comportement de Linq To Entities dans ce cas là.

     

    Ps: Après de plus amples recherche j'ai remarqué que l'insert se fait suivant l'ordre du RowId interne à SQL Server. Je suppose que juste avant l'appel des insertions, Linq To Entities fait un Order By sur ce RowId, ce qui expliquerai le problème.


    Il est bon de savoir, mais il est préférable d'apprendre.
    lundi 20 septembre 2010 14:53
  • Bonjour,

    Pour la récupération de ces billes, elle sont (a mon avis) implicitement numérotés par Sql Server si aucune autre indication n'est fourni (Order By). Je pense que cette numérotation correspond a l'identifiant interne unique de la base.
    Si vous ne spécifiez pas de tri (ORDER BY), Sql Server ne peut pas garantir dans quel ordre vous allez récupérer les lignes (l'ordre est complétement aléatoire et dépend des données mise en cache au moment de la requête, des indexes utilisés, ...etc). Il ne font donc pas baser votre application sur ce tri aléatoire, mais en spécifier un tri explictement (ajoutez si nécessaire une colonne).

    Voyez les tables comme un ensemble mathématique...

    En ce qui concerne l'Entity Framework, ce dernier dispose d'un Tracker qui permet de suivre les entités. C'est ce tracker qui maintient une liste d'entité à mettre à jour et cette liste n'est nullement garantie comme ordonnée par Microsoft. On ne peut donc garantir que les entités seront envoyés dans un ordre précis.

    Cordialement


    Gilles TOURREAU - MVP C# - MCTS ADO .NET 3.5 - MCPD Windows Developper 3.5 - Architecte .NET/Consultant/Formateur - http://gilles.tourreau.fr
    • Marqué comme réponse ddv_ lundi 20 septembre 2010 15:22
    lundi 20 septembre 2010 15:17
    Modérateur
  • Merci j'ai ma réponse,

    Je vais donc changer mon model et espérer qu'une future version permettra de rajouter une élément d'ordre à une relation (ce qui doit être souvent demandé je pense car la facilité apporté par la suppression de la table intermédiaire n'est pas négligeable).

     


    Il est bon de savoir, mais il est préférable d'apprendre.
    lundi 20 septembre 2010 15:21
  • Bonjour,

    en fait entity framework gére l'ordre de façon assez complexe pour pouvoir respecter des contraintes d'intégrité référentielle par exemple...

    Pour des insert qui n'aurait pas de dépendances il semble qu'entity framework ne gère pas l'ordre.

    Vous trouvez ici un lien sur ce point : row-insertion-order-entity-framework

    Concernant RowId j'imagine plutot que c'est le select qui utilise ce tri par défaut. Vous pourriez peut être obtenir un autre tri par défaut si un index existait sur la table...Néanmoins cela ne semble pas lié à l'ordre aléatoire des insertions.

    Cordialement

     

     

    • Marqué comme réponse ddv_ lundi 20 septembre 2010 15:24
    lundi 20 septembre 2010 15:22
  • "Unfortunately we don't currently track 'when' something was added to the context, and for efficiency reason we don't track entities in order preserving structures like lists. As a result we can't currently preserve the order of unrelated inserts."

     

    Voila une explication et peut être un espoir que cela changera dans le futur.



    Il est bon de savoir, mais il est préférable d'apprendre.
    lundi 20 septembre 2010 15:27