2011-07-09 1 views
1

J'insère un certain nombre d'instances d'un objet particulier dans une base de données à l'aide de Entity Framework. J'ai un seul contexte d'objet auquel j'attache plusieurs objets 'Product'. L'objet que j'insère n'a aucun lien avec une autre table de la base de données. C'est une entité purement autonome. J'utilise l'outil 'EFProf' pour profiler l'application. Lorsque j'appelle 'SaveChanges()' pour conserver mes entités 'Product' à SQL Server, EFProf m'avertit que j'ai l'anti-pattern 'Select N + 1'. Je ne vois pas comment cela est possible puisque j'insère simplement. Ma compréhension de 'Select N + 1' est qu'il se rapporte à la récupération d'objets inefficace. Je ne récupère rien, je l'insère seulement. Lorsque j'examine le SQL généré, je vois que Entity Framework a généré une instruction select en mon nom qui retourne l'ID de l'objet nouvellement inséré. Cette instruction select est effectuée pour chaque entité que j'insère. Est-ce que ceci pourrait être la cause du problème choisi de N + 1? Si oui, comment puis-je éviter cet anti-pattern lors de l'insertion d'un certain nombre d'entités du même type dans un seul appel à SaveChanges()?Entités d'encombrement Entity Framework entraînant un problème n + 1 de sélection

Le SQL généré est ci-dessous:

insert [dbo].[Products] 
    ([ProductName], 
    [ProductNum], 
    [Price], 
    [EntryDate], 
    [Description], 
    [Category], 
    [UnitsInStock]) 
values('TestProduct' /* @0 */, 
    0 /* @1 */, 
    0 /* @2 */, 
    '2011-07-09T17:14:49.00' /* @3 */, 
    'Category: Test Products - Name TestProduct' /* @4 */, 
    'Test Products' /* @5 */, 
    0 /* @6 */) 


select [Id] 
from [dbo].[Products] 
where @@ROWCOUNT > 0 
     and [Id] = scope_identity() 

Répondre

1

vous ne pouvez pas éviter que select supplémentaire. Une fois que vous utilisez des identifiants autogénérés dans la base de données et que votre modèle est correctement configuré pour eux, EF crée toujours cette sélection supplémentaire.

Vous n'avez pas besoin de vous embêter. Les performances de "insert en vrac" avec EF sont si terriblement mauvaises que ce choix supplémentaire lors de chaque insertion ne signifie rien. Ce que vous devez faire est le traitement lui-même car EF va créer un aller-retour séparé vers la base de données pour chaque enregistrement inséré et c'est le problème. Si vous voulez vraiment passer un grand nombre d'objets dans la base de données, vous voulez le faire souvent (ce n'est pas un travail ponctuel) et vous attendez des performances que vous devriez certainement rechercher pour d'autres solutions non EF. Par exemple déjà mentionné SqlBulkCopy.

Si vous avez juste besoin d'insérer quelques instances dans une opération, laissez-la simplement. C'est ainsi que fonctionne EF.

+0

C'est un commentaire vraiment utile merci. Je fais actuellement des tests de performance sur NHibernate et Entity Framework. C'est le premier cas de test que j'ai exécuté et mes résultats pour NHibernate sont tellement plus rapides (même sans batching) que je commence à me demander ce qui ne va pas avec mon code. J'ai mes cas de test mis en œuvre de manière identique à ce que je peux voir. Quand vous dites que les performances EF sont «terriblement mauvaises» pour les encarts en masse, sur quoi vous basez-vous?Je devrai écrire mes résultats si vous avez d'autres études ou articles décrivant ce problème qui serait extrêmement utile. Merci. – JMc

+0

C'est facile. Supposons que vous créez un objet avec des objets connexes dans votre application. Par exemple commander avec 50 articles de commande. Si vous avez une fonctionnalité appelée "batch batch" (NHibernate l'a si elle est utilisée correctement), vous ferez un aller-retour vers la base de données et elle contentera 51 insertions (1 commande, 50 items). Si vous faites la même chose avec EF, il fera séquentiellement 51 roundtripts vers la base de données contenant chacun une insertion simple. Roundtrip est la partie la plus lente de presque toutes les opérations que vous pouvez faire. –

0

Si vous essayez de faire un insert en vrac, il me semble que SCOPE_IDENTITY() est inutile, car il stocke une seule valeur scalaire - pas une plage. Je vais trahir mon ignorance de EF, mais convertit-il ce code en commande BULK INSERT, ou SQLBulkCopy, ou autre chose?

Si vous allez faire un insert à la fois, puis de vérifier @@ROWCOUNT d'abord au lieu de mettre dans la clause where semble être un meilleur modèle en général:

IF @@ROWCOUNT = 1 
BEGIN 
    SELECT [Id] 
     FROM [dbo].[Products] 
     WHERE [Id] = SCOPE_IDENTITY(); 
END 

Ou même pas prendre la peine du tout , car si le suivant est vide resultset, vous savez maintenant ce que le @@ROWCOUNT était:

SELECT [Id] 
    FROM [dbo].[Products] 
    WHERE [Id] = SCOPE_IDENTITY(); 
Questions connexes