2011-07-18 4 views
5

Existe-t-il une alternative à l'utilisation de .Contains() pour sélectionner des objets dans Entity Framework qui existent dans une liste spécifiée? Contains() fonctionne très bien si votre liste est petite, mais une fois que vous commencez à obtenir quelques milliers d'articles, la performance est terrible.Alternative à l'utilisation de IList.Contains (item.Id) dans Entity Framework pour les grandes listes?

return (from item in context.Accounts 
     where accountIdList.Contains(item.AccountId) 
     select item).ToList(); 

J'utilise EF 4.0, Net Framework 4.0 et SQL Server 2005. Je ne suis pas opposé à une solution SQL soit depuis la requête EF ne génère que prend une seconde pour fonctionner sur SQL pour environ 10k articles.

Répondre

1

J'ai trouvé une alternative qui s'exécute en environ une seconde en utilisant une procédure stockée SQL et une chaîne délimitée par des virgules pour le paramètre. Beaucoup mieux que les 5+ minutes EF a été en train de prendre en utilisant .Contains()

Il est exécuté à partir de mon code en utilisant les éléments suivants:

string commaDelmitedList = string.Join(",", accountIdList); 
return context.GetAccountsByList(commaDelmitedList).ToList(); 

Le StoredProcedure (simplifié) ressemble à ceci:

SELECT * 
FROM Accounts as T1 WITH (NOLOCK) 
INNER JOIN (
    SELECT Num FROM dbo.StringToNumSet(@commaDelimitedAccountIds, ',') 
) as [T2] ON [T1].[AccountId] = [T2].[num] 

Et la fonction définie par l'utilisateur dbo.StringToNumSet() ressemble à ceci:

CREATE FUNCTION [dbo].[StringToNumSet] (
@TargetString varchar(MAX), 
@SearchChar varchar(1) 
) 
RETURNS @Set TABLE (
num int not null 
) 
AS 
BEGIN 
DECLARE @SearchCharPos int, @LastSearchCharPos int 
SET @SearchCharPos = 0 
WHILE 1=1 
BEGIN 
    SET @LastSearchCharPos = @SearchCharPos 
    SET @SearchCharPos = CHARINDEX(@SearchChar, @TargetString, @SearchCharPos + 1) 
    IF @SearchCharPos = 0 
    BEGIN 
    INSERT @Set(num) VALUES (SUBSTRING(@TargetString, @LastSearchCharPos + 1, DATALENGTH(@TargetString))) 
    BREAK 
    END 
    ELSE 
    INSERT @Set(num) VALUES (SUBSTRING(@TargetString, @LastSearchCharPos + 1, @SearchCharPos - @LastSearchCharPos - 1)) 
END 
RETURN 
END 
0

Serait-il viable de simplement vous lire des informations dans la mémoire puis faire les recherches.

J'ai trouvé que dans la plupart des cas, vous deviez travailler avec de grandes quantités de données si vous pouviez sortir avec la lecture de toutes les données en mémoire et ensuite faire les recherches beaucoup plus rapidement.

+0

Non, le base de données a quelques millions de comptes et les données que je veux de la base de données comprend quelques jointures qui rend le résultat final encore plus grand – Rachel

+0

@Rachel, avez-vous essayé d'utiliser un profileur pour voir quelles requêtes sont générées. http://efprof.com/ a un parcours gratuit et pourrait aider avec ceci. – Jethro

+0

Oui, j'ai et la requête s'exécute bien. Il faut environ une seconde pour tirer environ 10 000 enregistrements sur quelques millions. La partie que je trouve inacceptable est que EF prend environ 5 minutes pour générer l'instruction SQL en utilisant .Contains() sur une liste d'environ 10k éléments. – Rachel

0

Contains est déjà traduit en une instruction SQL massive WHERE IN, donc ce n'est pas vraiment un problème. Toutefois, vous ne devriez pas évaluer la requête avec impatience, car cela exécutera la requête chaque fois que vous appelez cette méthode. Tirez parti de la nature de linq-to-entities et laissez la requête être évaluée lorsque vous l'itérez réellement.

+0

Pouvez-vous poster du code montrant un Exemple? Je suis encore relativement nouveau à linq-to-entities. De plus, les performances de l'instruction SQL sont correctes (environ une seconde à traiter), c'est la construction de l'instruction SQL qui prend EF aussi longtemps. – Rachel

+0

Hm, d'accord. Supprimer le 'ToList' serait l'exemple. Bien que je puisse comprendre, cela peut prendre du temps. Serait-il possible de garder le 'accountIdList' dans la base de données en quelque sorte? Vous pouvez alors utiliser une simple jointure à la place d'une clause "WHERE IN" générée automatiquement. – Femaref

+0

Le 'accountIdList' est basé sur l'entrée de l'utilisateur, donc non.Il peut être lu à partir d'un fichier Excel de accountIds, ou il est généré sur la base d'autres entrées de l'utilisateur tel lot de vente, lot de paiement, etc – Rachel

Questions connexes